├── .gitignore ├── .prettierignore ├── README.md ├── dist ├── bundle.js └── index.html ├── package-lock.json ├── package.json ├── src ├── client │ ├── components │ │ ├── AnonIdChoicePage.js │ │ ├── App.js │ │ ├── MainChat.js │ │ └── SignInPage.js │ ├── context │ │ ├── SocketContext.js │ │ └── UserContext.js │ ├── index.html │ ├── index.js │ ├── popup-auth-window-test.js │ └── styles │ │ ├── index.scss │ │ └── variables.scss └── server │ ├── github │ ├── controllers │ │ ├── githubController.js │ │ └── requests.js │ └── github.js │ ├── ids │ ├── controllers │ │ └── id.js │ └── ids.js │ ├── messages │ ├── controller │ │ └── messagesController.js │ └── messages.js │ ├── models │ └── elephantsql.js │ ├── redis │ └── redis.js │ ├── server.js │ ├── utils │ └── dbUtils.js │ └── ws │ └── ws.js ├── utils ├── createTables.js ├── dump.rdb ├── hashAllUsersUtil.js ├── hashMessages.js └── populateUserDB.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | dump.rdb 4 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | utils/populateUserDB.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSAnon 2 | An anonymous board to ask for help, coding or otherwise. 3 | 4 | WELCOME, ITERATORS! 5 | 6 | In order to work on this project, you're going to need a few things: 7 | 8 | 1. A Postgres server (either local or cloud) 9 | 10 | 2. A Redis server (we ran a local instance, see https://redis.io/topics/quickstart ) 11 | 12 | 3. A .env file on the root directory, which has the following: 13 | 14 | DB_URL=postgres:\ 15 | 16 | CLIENT_ID=\ 17 | 18 | CLIENT_SECRET=\ 19 | 20 | JWT_SECRET=\ 21 | 22 | PROJECT_NAME=\ 23 | 24 | 4. On your github Oauth, set the redirect url to localhost:8080/auth/callback 25 | 26 | 5. Load a csv with the list of allowable usernames, complete logic for the hashAllUsersUtil file. Run this util to populate your db with the hashed usernames. 27 | 28 | 6. Run populateUserDB to fill the users table with pokemon names and urls. 29 | 30 | 31 | POSTGRES SERVER: 32 | 33 | Schemas: 34 | 35 | 36 | MESSAGE: 37 | 38 | -------------------------------------------- 39 | 40 | PIC TIMESTAMP 41 | 42 | USERNAME MESSAGE 43 | 44 | -------------------------------------------- 45 | 46 | TABLE hash_list 47 | COLUMN bcrypt_hash varchar 48 | 49 | TABLE messages 50 | COLUMN id int PRIMARY KEY 51 | COLUMN timestamp date 52 | COLUMN user_id int FOREIGN KEY 53 | COLUMN message varchar 54 | 55 | TABLE users 56 | COLUMN user_id int PRIMARY KEY 57 | COLUMN username varchar 58 | COLUMN pic_url varchar 59 | 60 | SERVERSIDE: 61 | 62 | REDIS (key/value pairs) - 63 | 64 | Stores the following key / value pairs: 65 | 66 | - username : 'true' 67 | - socketID: username 68 | 69 | 70 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CSAnon 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a", 3 | "version": "1.0.0", 4 | "description": "An anonymous board to ask for help, coding or otherwise.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production nodemon src/server/server.js", 8 | "dev": "cross-env NODE_ENV=development concurrently \"nodemon src/server/server.js\" \"webpack-dev-server --open\"", 9 | "build": "cross-env NODE_ENV=production webpack", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ny19-jigglypuff/CSAnon.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/ny19-jigglypuff/CSAnon/issues" 21 | }, 22 | "homepage": "https://github.com/ny19-jigglypuff/CSAnon#readme", 23 | "devDependencies": { 24 | "@babel/core": "^7.10.3", 25 | "@babel/preset-env": "^7.10.3", 26 | "@babel/preset-react": "^7.10.1", 27 | "babel-loader": "^8.1.0", 28 | "css-loader": "^3.6.0", 29 | "nodemon": "^2.0.4", 30 | "sass": "^1.26.9", 31 | "sass-loader": "^8.0.2", 32 | "style-loader": "^1.2.1", 33 | "webpack": "^4.43.0", 34 | "webpack-cli": "^3.3.12", 35 | "webpack-dev-server": "^3.11.0" 36 | }, 37 | "dependencies": { 38 | "bcrypt": "^5.0.0", 39 | "concurrently": "^5.2.0", 40 | "cookie-parser": "^1.4.5", 41 | "cookie-session": "^1.4.0", 42 | "cross-env": "^7.0.2", 43 | "dotenv": "^8.2.0", 44 | "express": "^4.17.1", 45 | "jsonwebtoken": "^8.5.1", 46 | "moment": "^2.27.0", 47 | "pg": "^8.2.1", 48 | "react": "^16.13.1", 49 | "react-dom": "^16.13.1", 50 | "react-hot-loader": "^4.12.21", 51 | "react-router": "^5.2.0", 52 | "react-router-dom": "^5.2.0", 53 | "redis": "^3.0.2", 54 | "socket.io": "^2.3.0", 55 | "socket.io-client": "^2.3.0", 56 | "superagent": "^5.2.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/client/components/AnonIdChoicePage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import SocketContext from "../context/SocketContext"; 4 | // import UserContext from '../context/UserContext'; 5 | 6 | export default function AnonIdChoicePage() { 7 | /* 8 | constructor (props) { 9 | super(props) 10 | this.state = { 11 | anonId : {username: ', userURL''}, 12 | isLoading: true 13 | } 14 | } 15 | */ 16 | const [anonId, setAnonId] = useState({ username: "", userURL: "" }); 17 | const [isLoading, setIsLoading] = useState(true); 18 | const socket = useContext(SocketContext); 19 | 20 | // TEST ! 21 | // const user = useContext(UserContext); 22 | 23 | const handleRerollClick = () => { 24 | //this.setState({isLoading: true}) 25 | setIsLoading(true); 26 | }; 27 | 28 | const handleGoToChatClick = () => { 29 | // TEST: save username in session storage ? 30 | // socket.emit("signin", { username: sessionStorage.getItem('username') }); 31 | socket.emit("signin", { username: anonId.username }); 32 | }; 33 | 34 | const getNewIdBecauseError = () => { 35 | //this separate request is to prevent the backend from saving an anonId cookie 36 | //until an ID with a non-broken image is found 37 | let rt = '/id/true'; 38 | fetch(rt) 39 | .then(res => res.json()) 40 | .then(result => { 41 | console.log(result); 42 | setIsLoading(false); 43 | setAnonId(result); 44 | }); 45 | } 46 | 47 | const getNewId = () => { 48 | let rt = '/id/false'; 49 | fetch(rt) 50 | .then(res => res.json()) 51 | .then(result => { 52 | // console.log(result); 53 | setIsLoading(false); 54 | setAnonId(result); 55 | }); 56 | }; 57 | 58 | //on initial render, get an ID asynchronously 59 | if (isLoading) { 60 | getNewId(); 61 | } 62 | 63 | return ( 64 |
65 | {isLoading ?

Loading...

: ( 66 | <> 67 | {/* TODO: add log out functionality */} 68 | {/**/} 69 | 70 |

{anonId.username}

71 |
72 | 73 | 78 | Go to chat 79 | 80 |
81 | 82 | )} 83 |
84 | ); 85 | } 86 | 87 | /* 88 | 89 | Receive from '/id/ endpoint: 90 | 91 | userID = { 92 | username: String, 93 | userURL: String, 94 | } 95 | 96 | **keep username in session storage** 97 | 98 | on go to chat, submit username to '/id/pick/' (will register in redis) 99 | 100 | */ 101 | -------------------------------------------------------------------------------- /src/client/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'; 3 | import io from 'socket.io-client'; 4 | import SocketContext from '../context/SocketContext'; 5 | import AnonIdChoicePage from './AnonIdChoicePage'; 6 | import SignInPage from './SignInPage'; 7 | import MainChat from './MainChat'; 8 | import UserContext from '../context/UserContext'; 9 | 10 | //loggedIn = is there access token in browser 11 | //if loggedIn 12 | // render AnonChoiceIdPage component 13 | //if not loggedIn 14 | // redirect to /signin 15 | 16 | const socket = io(); 17 | 18 | export default function App() { 19 | //Check if cookie called 'token' exists in browser 20 | const loggedIn = document.cookie.split(';').some((item) => item.trim().startsWith('token=')); 21 | 22 | // TEST! wrap BrowserRouter in UserContext Provider ?! 23 | // 24 | 25 | return ( 26 | //Provide the Context to the App by wrapping it in a Provider 27 | //The value prop will be available to all the child Components via the useContext hook 28 | 29 | 30 | 31 | {/* csanon.com */} 32 | 33 | {loggedIn ? : } 34 | 35 | {/* csanon.com/signin */} 36 | 37 | 38 | 39 | {/* csanon.com/chat */} 40 | }> 41 | 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/client/components/MainChat.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useContext, useLayoutEffect } from 'react'; 2 | import SocketContext from '../context/SocketContext'; 3 | 4 | export default function MainChat(props) { 5 | const [messageData, setMessageData] = useState([]); 6 | const [users, setUsers] = useState(0) 7 | const [userlist, setUserList] = useState([]) 8 | const inputMessageRef = useRef(null); 9 | const chatRef = useRef(null); 10 | const socket = useContext(SocketContext); 11 | const inputSearchRef = useRef(null) 12 | 13 | const handleSearch = (e) => { 14 | if (inputSearchRef.current.value.length !== 0) { 15 | const matchedMessages = []; 16 | socket.disconnect(true) 17 | messageData.forEach((element) => { 18 | const lowerCasedMassage = element.message.toLowerCase() 19 | if (lowerCasedMassage.search(inputSearchRef.current.value) !== -1) { 20 | matchedMessages.push(element) 21 | } 22 | }) 23 | setMessageData(matchedMessages) 24 | } 25 | else { 26 | getData() 27 | socket.disconnect(false) 28 | } 29 | e.preventDefault() 30 | } 31 | const handleSendClick = (e) => { 32 | if (inputMessageRef.current.value.length > 1) { 33 | e.preventDefault(); 34 | const data = { 35 | message: inputMessageRef.current.value, 36 | username: props.location.state.username, 37 | }; 38 | inputMessageRef.current.value = ''; 39 | socket.emit('message', data); 40 | } 41 | }; 42 | 43 | socket.on('count', (data) => { 44 | setUsers(data) 45 | }) 46 | 47 | socket.on('userlist', (data) => { 48 | setUserList(data) 49 | }) 50 | 51 | socket.on('data', (data) => { 52 | console.log(data) 53 | }) 54 | 55 | socket.on('newMessage', (data) => { 56 | //{username, userURL, timestamp, message} 57 | const newArr = [...messageData]; 58 | newArr.push(data); 59 | setMessageData(newArr); 60 | }); 61 | 62 | //TODO: add function for request more messages on scroll up 63 | //TODO: not scrolling to bottom on initial load 64 | const getData = () => { 65 | fetch('/messages/all') 66 | .then((res) => res.json()) 67 | .then((res) => { 68 | setMessageData(res); 69 | }) 70 | } 71 | 72 | if (messageData.length === 0) { 73 | getData() 74 | } 75 | 76 | //make sure the 'chat' div is scrolled to the bottom with every render 77 | useLayoutEffect(() => { 78 | chatRef.current.scrollTop = chatRef.current.scrollHeight; 79 | }) 80 | 81 | return ( 82 |
83 |
84 |

{props.location.state.username}

85 | 86 |

Users: {users}

87 | {userlist.map((el) => (
{el}
))} 88 | {/* TODO: add log out functionalities */} 89 | {/* 90 | */} 91 |
92 | 93 |
94 |
95 | 96 |
97 |
98 | {/* assumes most recent message is at the end of the array */} 99 | {messageData.map((message) => ( 100 | 103 | ))} 104 |
105 |
106 | 107 | | 108 | 109 |
110 |
111 |
112 | ); 113 | } 114 | 115 | function Message(props) { 116 | const className = 'messageContainer' + (props.yourName === props.username ? ' self' : ''); 117 | return ( 118 |
119 | 120 |
121 | {props.username} 122 |

{props.message}

123 |
124 |
125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /src/client/components/SignInPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | //{/* show github oauth button */ } 4 | //{/* onclick, send to /auth/user endpoint */ } 5 | //{/* server redirects client to auth url */ } 6 | //{/* successful signin -> goes back to server */ } 7 | //{/* backend will redirect the user to the front end, with github username and JWT cookie*/ } 8 | //{/* if approved CS user, backend will redirect the user to the front end on '/', with JWT cookie */ } 9 | //{/* if not approved CS user -> server should send back some error */ } 10 | 11 | export default function SignInPage() { 12 | return ( 13 |
14 |

CS Anonymous

15 |
16 | Sign in to GITHUB 17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/client/context/SocketContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | //Create a context using React's Context API to provide to the App 4 | const SocketContext = React.createContext(); 5 | 6 | export default SocketContext; -------------------------------------------------------------------------------- /src/client/context/UserContext.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | 3 | // const UserContext = React.createContext(); 4 | 5 | // export default UserContext; -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CSAnon 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './components/App'; 3 | import { render } from 'react-dom'; 4 | 5 | import './styles/index.scss'; 6 | 7 | render(, document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /src/client/popup-auth-window-test.js: -------------------------------------------------------------------------------- 1 | //Authorization popup window code 2 | //Example from: 3 | //https://gist.github.com/asith-w/95d7f69e0e957bf72a5703ba45c6a9e8 4 | function ShowAuthWindow(options) { 5 | console.log('ee'); 6 | options.windowName = 'GitHubOAuth'; // should not include space for IE 7 | options.windowOptions = options.windowOptions || 'location=0,status=0,width=800,height=400'; 8 | options.callback = 9 | options.callback || 10 | function () { 11 | window.location.reload(); 12 | }; 13 | var that = this; 14 | console.log(options.path); 15 | that._oauthWindow = window.open(options.path, options.windowName, options.windowOptions); 16 | that._oauthInterval = window.setInterval(function () { 17 | if (that._oauthWindow.closed) { 18 | window.clearInterval(that._oauthInterval); 19 | options.callback(); 20 | } 21 | }, 1000); 22 | } 23 | 24 | //create new oAuth popup window and monitor it 25 | //ShowAuthWindow({ 26 | // path: "https://accounts.google.com/o/oauth2/auth?client_id=656984324806-sr0q9vq78tlna4hvhlmcgp2bs2ut8uj8.apps.googleusercontent.com&response_type=token&redirect_uri=https%3A%2F%2Fadodson.com%2Fhello.js%2Fredirect.html&state=%7B%22client_id%22%3A%22656984324806-sr0q9vq78tlna4hvhlmcgp2bs2ut8uj8.apps.googleusercontent.com%22%2C%22network%22%3A%22google%22%2C%22display%22%3A%22popup%22%2C%22callback%22%3A%22_hellojs_2u7fxpwv%22%2C%22state%22%3A%22%22%2C%22redirect_uri%22%3A%22https%3A%2F%2Fadodson.com%2Fhello.js%2Fredirect.html%22%2C%22scope%22%3A%22basic%22%7D&scope=https://www.googleapis.com/auth/plus.me%20profile", 27 | // callback: function() 28 | // { 29 | // console.log('callback'); 30 | // } 31 | //}); -------------------------------------------------------------------------------- /src/client/styles/index.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; //this is only supported by Dart SASS, so must use sass npm package instead of node-sass 2 | //node-sass uses LibSass 3 | 4 | body { 5 | margin: 0; 6 | font-family: $font-family; 7 | } 8 | 9 | .mainContainer { 10 | display: flex; 11 | background-color: $medium-grey; 12 | height: 100vh; 13 | color: white; 14 | } 15 | 16 | .btn, button:not([type=submit]) { 17 | color: white; 18 | background-color: $blue; 19 | text-decoration: none; 20 | padding: 1rem; 21 | border-radius: $border-radius; 22 | border: none; 23 | font-size: 1rem; 24 | } 25 | 26 | .mainContainer.signInPage { 27 | flex-direction: column; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | .mainContainer.anonChoice { 33 | flex-direction: column; 34 | align-items: center; 35 | justify-content: center; 36 | 37 | img { 38 | width: 200px; 39 | height: auto; 40 | } 41 | .row { 42 | display: flex; 43 | } 44 | .btn, button { 45 | margin: 0.5rem; 46 | } 47 | } 48 | 49 | .mainContainer.mainChat { 50 | display: flex; 51 | height: 100vh; 52 | 53 | .sidebar { 54 | display: flex; 55 | flex-direction: column; 56 | justify-content: flex-start; 57 | align-items: center; 58 | flex: 0 0 250px; 59 | background-color: $dark-grey; 60 | color: white; 61 | } 62 | .chatContainer { 63 | flex: 1 1 100%; 64 | display: flex; 65 | flex-direction: column; 66 | justify-content: flex-end; 67 | align-items: center; 68 | background-color: $medium-grey; 69 | box-sizing: border-box; 70 | 71 | .chat { 72 | flex: 0 1 100%; 73 | overflow-y: auto; 74 | padding: 0.5rem; 75 | box-sizing: border-box; 76 | width: 100%; 77 | 78 | .messageContainer { 79 | display: flex; 80 | align-items: center; 81 | .textContainer { 82 | display: flex; 83 | flex-direction: column; 84 | align-items: flex-start; 85 | max-width: 80%; 86 | } 87 | p { 88 | position: relative; 89 | margin: 0; 90 | background-color: $blue; 91 | border-radius: $border-radius; 92 | padding: 0.5rem; 93 | box-sizing: border-box; 94 | color: white; 95 | overflow-wrap: break-word; 96 | max-width: 50vw; 97 | font-size: 0.9rem; 98 | box-shadow: 0 1px 6px 2px rgba(54, 62, 70, 0.5), 0 1px 1px 1px rgba(54, 62, 70, 0.7); 99 | } 100 | span { 101 | margin-bottom: 5px; 102 | color: $very-light-grey; 103 | font-size: 0.7rem; 104 | } 105 | img { 106 | border-radius: 50%; 107 | width: 100px; 108 | height: auto; 109 | } 110 | } 111 | .messageContainer.self { 112 | flex-direction: row-reverse; 113 | align-self: flex-end; 114 | .textContainer { 115 | align-items: flex-end; 116 | text-align: right; 117 | } 118 | } 119 | } 120 | 121 | .form, form { 122 | display: flex; 123 | flex-direction: row; 124 | width: calc(100% - 1rem); 125 | padding: 0.7rem; 126 | box-sizing: border-box; 127 | background-color: $light-grey; 128 | margin: 1rem; 129 | border-radius: $border-radius; 130 | color: $very-light-grey; 131 | 132 | input { 133 | background-color: transparent; 134 | color: white; 135 | flex: 0 1 100%; 136 | border: none; 137 | font-size: 0.9rem; 138 | } 139 | input::placeholder { 140 | color: $very-light-grey; 141 | } 142 | input:focus { 143 | outline: none; 144 | } 145 | button { 146 | background-color: transparent; 147 | border: none; 148 | color: $very-light-grey; 149 | padding: 0 10px; 150 | } 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/client/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $font-family: "HK Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif; 2 | $dark-grey: #363E46; 3 | $medium-grey: lighten($dark-grey, 10%); 4 | $light-grey: lighten($dark-grey, 20%); 5 | $very-light-grey: lighten($dark-grey, 40%); 6 | $blue: #2A95EB; 7 | $border-radius: 4px; -------------------------------------------------------------------------------- /src/server/github/controllers/githubController.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv").config(); // loads the .env file onto the process.env object 2 | const jwt = require("jsonwebtoken"); 3 | const bcrypt = require("bcrypt"); 4 | 5 | const githubController = {}; 6 | 7 | const { requestToken, requestUser, checkMembership } = require("./requests"); 8 | 9 | const db = require("../../models/elephantsql"); 10 | 11 | // loads the information from the .env file 12 | const CLIENT_ID = process.env.CLIENT_ID; 13 | const JWT_SECRET = process.env.JWT_SECRET; 14 | 15 | githubController.redirect = (req, res, next) => { 16 | const baseURL = "https://github.com/login/oauth/authorize"; 17 | // VERY IMPORTANT TO DEFINE SCOPES!!!!!!!!! 18 | // https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ 19 | // const scope = 'user'; 20 | const scope = "read:org"; 21 | res.redirect(`${baseURL}?client_id=${CLIENT_ID}&scope=${scope}`); 22 | }; 23 | 24 | githubController.callback = (req, res, next) => { 25 | // github returns a code in a query param 26 | const { code } = req.query; 27 | console.log("code", code); 28 | if (!code) { 29 | return next({ 30 | error: { code: 403, message: "User Not Authorized By Github" }, 31 | }); 32 | } 33 | 34 | requestToken(code).then((result) => 35 | requestUser(result, res) 36 | .then(({ body }) => { 37 | // console.log(body); 38 | res.locals.login = body.login; 39 | // console.log(res.locals) 40 | return next(); 41 | }) 42 | .catch((err) => { 43 | console.log(err.status); 44 | console.log(err.message); 45 | return next(err); 46 | }) 47 | ); 48 | }; 49 | 50 | githubController.approveUser = async (req, res, next) => { 51 | const githubHandle = res.locals.login; 52 | checkMembership(githubHandle, res.locals.access_token) 53 | .then((response) => { 54 | // should give 204 status in response 55 | // if yes - good to go, allowed to access chat (member of the organization) 56 | console.log(response.status); 57 | if (response.status === 204) { 58 | res.locals.user = githubHandle; 59 | return next(); 60 | } 61 | else res.redirect('/signin'); 62 | }) 63 | .catch((err) => { 64 | res.redirect('/signin'); 65 | //console.log(err.status); 66 | //console.log(err.message); 67 | //return next(err); 68 | }); 69 | 70 | // get all rows of the hash table 71 | // const queryString = `SELECT bcrypt_hash FROM hash_table`; 72 | // db.query(queryString) 73 | // .then((result) => { 74 | // if (!result.rows.length) { 75 | // res.status(403).json({ error: { message: 'Hash table error' } }); 76 | // } else { 77 | // // for each item in the hash_table 78 | // for (let i = 0; i < result.rows.length; i++) { 79 | // // check if the plaintext github handle matches the hashed_handle 80 | // let match = bcrypt.compare(githubHandle, result.rows[i].bcrypt_hash); 81 | // if (match) { 82 | // // res.locals.user = result.rows[i].bcrypt_hash; 83 | // return next(); 84 | // } 85 | // } 86 | // } 87 | // //TODO: Redirect to /signin route if no match 88 | // next(); 89 | // }) 90 | // .catch((err) => next(err)); 91 | }; 92 | 93 | githubController.createJWT = async (req, res, next) => { 94 | const hashedHandle = res.locals.user; 95 | jwt.sign({ username: hashedHandle }, JWT_SECRET, (err, token) => { 96 | if (err) return next(err); 97 | res.locals.token = token; 98 | next(); 99 | }); 100 | }; 101 | 102 | githubController.setCookie = (req, res, next) => { 103 | const token = res.locals.token; 104 | res.cookie("token", token); 105 | console.log('set cookie is running') 106 | next(); 107 | }; 108 | 109 | // logic to guard all protected routes 110 | githubController.cookieVerifier = (req, res, next) => { 111 | if (!req.cookies.token) return res.redirect("/"); 112 | const { token } = req.cookies; 113 | jwt.verify(token, JWT_SECRET, (err, decoded) => { 114 | if (err) return console.error("JWT could not be verified"); 115 | const queryString = `SELECT bcrypt_hash FROM hash_table WHERE bcrypt_hash = '${decoded.username}'`; 116 | db.query(queryString) 117 | .then((result) => { 118 | // if no match, redirect the user to the main page 119 | if (!result.rows.length) { 120 | res.redirect("/"); 121 | } else { 122 | // if match, pass them to next middleware 123 | return next(); 124 | } 125 | }) 126 | .catch((err) => next(err)); 127 | }); 128 | }; 129 | 130 | module.exports = githubController; 131 | -------------------------------------------------------------------------------- /src/server/github/controllers/requests.js: -------------------------------------------------------------------------------- 1 | const request = require("superagent"); 2 | 3 | const CLIENT_ID = process.env.CLIENT_ID; 4 | const CLIENT_SECRET = process.env.CLIENT_SECRET; 5 | const PROJECT_NAME = process.env.PROJECT_NAME; 6 | 7 | const requestToken = (code) => { 8 | return request 9 | .post("https://github.com/login/oauth/access_token") 10 | .send({ 11 | client_id: CLIENT_ID, 12 | client_secret: CLIENT_SECRET, 13 | code, 14 | }) 15 | .set('Accept', 'application/json'); 16 | }; 17 | 18 | const requestUser = (result, response) => { 19 | const { access_token, scope } = result.body; 20 | console.log("user scope", scope); 21 | // console.log('access token:', access_token); 22 | response.locals.access_token = access_token; 23 | return ( 24 | request 25 | .get("https://api.github.com/user") 26 | // User-Agent header is required by GitHub OAuth, and value is project name or client_id 27 | .set("User-Agent", PROJECT_NAME) 28 | // Authorization: token OAUTH-TOKEN <- space after token is VERY important 29 | .set("Authorization", "token " + access_token) 30 | ); 31 | }; 32 | 33 | const checkMembership = (githubHandle, access_token) => { 34 | const baseURL = "https://api.github.com"; 35 | const org = "CodesmithLLC"; 36 | const route = `/orgs/${org}/members/${githubHandle}`; 37 | return ( 38 | request 39 | .get(baseURL + route) 40 | // User-Agent header is required by GitHub OAuth, and value is project name or client_id 41 | .set("User-Agent", PROJECT_NAME) 42 | // Authorization: token OAUTH-TOKEN <- space after token is VERY important 43 | .set("Authorization", "token " + access_token) 44 | ); 45 | }; 46 | 47 | module.exports = { requestToken, requestUser, checkMembership }; 48 | -------------------------------------------------------------------------------- /src/server/github/github.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const { 3 | redirect, 4 | callback, 5 | approveUser, 6 | createJWT, 7 | setCookie, 8 | } = require('./controllers/githubController'); 9 | 10 | // handles initial redirect of the user to github for authorization 11 | router.get('/user', redirect); 12 | 13 | // handles the user coming back from github 14 | // router.get('/callback', callback, approveUser, createJWT, setCookie, (req, res) => { 15 | // res.status(200).redirect('/'); 16 | // }); 17 | router.get('/callback', callback, approveUser, createJWT, setCookie, (req, res) => { 18 | res.status(200).redirect('/'); 19 | }); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /src/server/ids/controllers/id.js: -------------------------------------------------------------------------------- 1 | const redis = require("../../redis/redis"); 2 | const db = require("../../models/elephantsql"); 3 | const e = require("express"); 4 | 5 | const idsController = {}; 6 | 7 | const NUM_OF_POKEMON = 964; // change to fit the number of available pokemon we have 8 | // const NUM_OF_POKEMON = 100; 9 | 10 | const pickRandomPokemonNumber = () => { 11 | return Math.floor(Math.random() * NUM_OF_POKEMON); 12 | }; 13 | 14 | const userIDIsTaken = (number) => { 15 | redis.get(number, (err, reply) => { 16 | if (err) return console.error("redis lookup error:", err); 17 | return Boolean(reply); 18 | }); 19 | }; 20 | 21 | // finds the pokemon name and pictureURL attached to the random user_id 22 | const getNameAndPicture = async (id) => { 23 | const queryString = `SELECT username, pic_url FROM users WHERE user_id = ${id}`; 24 | const result = await db.query(queryString); 25 | if (result.rows.length) { 26 | const { username, pic_url } = result.rows[0]; 27 | // TEST !!! 28 | // res.cookie('username', username); 29 | return { username, userURL: pic_url }; 30 | } else { 31 | console.error("no name and picture results found"); 32 | } 33 | }; 34 | 35 | // TEST!!! 36 | const getPic = async (username) => { 37 | const queryString = `SELECT pic_url FROM users WHERE username = '${username}'`; 38 | const result = await db.query(queryString); 39 | console.log("Result from getPic query ", result); 40 | if (result.rows.length) { 41 | return result.rows[0].pic_url; 42 | } else { 43 | console.log("no picture found in DB"); 44 | } 45 | }; 46 | 47 | // Query doesn't work for some reason !!! 48 | const matchUsernameToID = async (username) => { 49 | const queryString = `SELECT user_id FROM users WHERE username = "${username}"`; 50 | const response = await db.query(queryString); 51 | if (response.rows.length) { 52 | const { userID } = response.rows[0]; 53 | return userID; 54 | } else { 55 | console.error("user id not found when searched by name"); 56 | } 57 | }; 58 | 59 | idsController.getNewID = async (req, res, next) => { 60 | // console.log('Req.session: ', req.session); 61 | let userObject; 62 | let pic; 63 | let handleImageError = Boolean(req.params.handleImageError); 64 | // const saved = req.session.cookies.username; 65 | const saved = req.session._ctx.cookies.username; 66 | // const saved = req.session._ctx.IncomingMessage.cookies.username; 67 | console.log("Username cookie was saved ", saved); 68 | // console.log('Token in cookies ', req.session.cookies.token); 69 | console.log("Token in cookies ", req.session._ctx.cookies.token); 70 | //if (!saved) { //if no username cookie is saved 71 | // let randomUserID = pickRandomPokemonNumber(); 72 | // while (userIDIsTaken(randomUserID)) { 73 | // randomUserID = pickRandomPokemonNumber(); 74 | // } 75 | // userObject = await getNameAndPicture(randomUserID); 76 | 77 | //} 78 | //else { //if the client has a username cookie 79 | // pic = await getPic(saved); 80 | //} 81 | //res.locals.availableID = userObject; 82 | //if (!handleImageError) res.cookie('username', username); //only set username cookie if 83 | // console.log('Cookie is set ', username); 84 | //return next(); 85 | if (saved) { 86 | // userID = matchUsernameToID(req.session._ctx.cookies.username); 87 | pic = await getPic(saved); 88 | } else { 89 | userID = pickRandomPokemonNumber(); 90 | while (userIDIsTaken(userID)) { 91 | const userID = pickRandomPokemonNumber(); 92 | } 93 | userObject = await getNameAndPicture(userID); 94 | } 95 | if (!saved) { 96 | const { username } = userObject; 97 | res.cookie("username", username); 98 | } 99 | userObject = { username: saved, userURL: pic}; 100 | console.log(userObject); 101 | res.locals.availableID = userObject; 102 | return next(); 103 | 104 | // ***** BEFORE WAS: 105 | // let randomUserID = pickRandomPokemonNumber(); 106 | // while (userIDIsTaken(randomUserID)) { 107 | // randomUserID = pickRandomPokemonNumber(); 108 | // } 109 | // const userObject = await getNameAndPicture(randomUserID); 110 | // res.locals.availableID = userObject; 111 | // // TEST !!! 112 | // const { username } = userObject; 113 | // // TEST ! 114 | // console.log('Cookie is set ', username); 115 | // res.cookie('username', username); 116 | // return next(); 117 | }; 118 | 119 | // TEST !!! 120 | // idsController.getSavedId = async (req, res, next) => { 121 | // const userID = matchUsernameToID(req.session.cookies.username); 122 | // const userObject = await getNameAndPicture(userID); 123 | // res.locals.availableID = userObject; 124 | // return next(); 125 | // }; 126 | 127 | module.exports = idsController; 128 | -------------------------------------------------------------------------------- /src/server/ids/ids.js: -------------------------------------------------------------------------------- 1 | const idsRouter = require('express').Router(); 2 | const idsController = require('./controllers/id'); 3 | // const { cookieVerifier } = require('../github/controllers/githubController'); 4 | 5 | // TEST !!! 6 | // idsRouter.get('/pick', idsController.getSavedID, (req, res) => { 7 | // res.status(200).json(res.locals.availableID); 8 | // }); 9 | 10 | idsRouter.get('/:handleImageErr', idsController.getNewID, (req, res) => { 11 | res.status(200).json(res.locals.availableID); 12 | }); 13 | 14 | module.exports = idsRouter; 15 | -------------------------------------------------------------------------------- /src/server/messages/controller/messagesController.js: -------------------------------------------------------------------------------- 1 | const db = require('../../models/elephantsql'); 2 | 3 | const messagesController = {}; 4 | 5 | const getAllMessages = async () => { 6 | // selects the 100 most recent message and created at keys from the database, 7 | // along with the username and pic_url of that message 8 | const queryString = `SELECT messages.message, messages.timestamp, users.username, users.pic_url FROM messages INNER JOIN users ON messages.user_id = users.user_id ORDER BY id DESC LIMIT 100 ;`; 9 | const results = await db.query(queryString); 10 | if (results.rows.length) { 11 | const messages = results.rows 12 | // provides the oldest messages first 13 | .reverse() 14 | // front end needs the pic_url provided as userURL 15 | // TODO: get the database in line with the needs of the front end 16 | .map((message) => ({ ...message, userURL: message.pic_url })); 17 | return messages; 18 | } else { 19 | console.error('could not get messages from database'); 20 | } 21 | }; 22 | 23 | messagesController.getAll = async (req, res, next) => { 24 | const allMessages = await getAllMessages(); 25 | res.locals.messages = allMessages; 26 | next(); 27 | }; 28 | 29 | module.exports = messagesController; 30 | -------------------------------------------------------------------------------- /src/server/messages/messages.js: -------------------------------------------------------------------------------- 1 | const messagesRouter = require('express').Router(); 2 | const messagesController = require('./controller/messagesController'); 3 | // const { cookieVerifier } = require('../github/controllers/githubController'); 4 | 5 | messagesRouter.get('/all', messagesController.getAll, (req, res) => { 6 | const messages = res.locals.messages; 7 | res.status(200).json(messages); 8 | }); 9 | 10 | module.exports = messagesRouter; 11 | -------------------------------------------------------------------------------- /src/server/models/elephantsql.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const connectionString = process.env.DB_URL; 3 | 4 | const pool = new Pool({ 5 | connectionString: connectionString, 6 | }); 7 | 8 | module.exports = { 9 | query: (text, params, callback) => { 10 | // console.log('executed query', text); 11 | return pool.query(text, params, callback); 12 | }, 13 | }; -------------------------------------------------------------------------------- /src/server/redis/redis.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | const redisClient = redis.createClient(); 3 | 4 | redisClient.on('ready', () => console.log('redis server ready')); 5 | 6 | redisClient.on('error', (error) => console.error('redis server error:', error)); 7 | 8 | redisClient.on('end', () => console.log('redis server connection closed')); 9 | 10 | module.exports = redisClient; 11 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | // TEST ! 4 | const cookieSession = require('cookie-session'); 5 | 6 | const app = express(); 7 | const http = require('http').createServer(app); 8 | 9 | const githubRouter = require('./github/github'); 10 | const idsRouter = require('./ids/ids'); 11 | const messagesRouter = require('./messages/messages'); 12 | const cookieParser = require('cookie-parser'); // npm install cookie-parser 13 | 14 | // will run on port 3000 for development, 15 | // PORT env variable will be set and available at deployment 16 | const PORT = process.env.PORT || 3000; 17 | 18 | // requires a locally running redis instance 19 | // install at https://redis.io/topics/quickstart 20 | const redis = require('./redis/redis'); 21 | 22 | // starts a socket.io server, wrapped around the server 23 | // listening on PORT 24 | const io = require('./ws/ws')(http); 25 | 26 | app.use(express.json()); 27 | app.use(express.urlencoded({ extended: false })); 28 | 29 | app.use(cookieParser()); // we need to add this line to have a chance to read the req.cookies. 30 | 31 | 32 | // ***** TEST ***** 33 | // app.set('trust proxy', 1); // trust first proxy 34 | 35 | app.use(cookieSession({ 36 | name: 'session', 37 | keys: ['key1'], 38 | // Cookie Options 39 | maxAge: 24 * 60 * 60 * 1000, // 24 hours 40 | sameSite: true 41 | })); 42 | 43 | // ******************** 44 | 45 | 46 | // handles github OAuth flow 47 | app.use('/auth', githubRouter); 48 | 49 | // handles the registration of userIDs 50 | app.use('/id', idsRouter); 51 | 52 | // returns the list of messages stored in the message database 53 | app.use('/messages', messagesRouter); 54 | 55 | // serves the index.html file at the root route to allow React Router to 56 | // handle all routes other than the ones defined above 57 | app.get('/bundle.js', (req, res) => res.sendFile(path.resolve(__dirname, '../../dist/bundle.js'))); 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '../../dist/index.html'))); 61 | } 62 | 63 | if (process.env.NODE_ENV === 'development') { 64 | app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '../client/index.html'))); 65 | } 66 | 67 | 68 | http.listen(PORT, () => console.log(`listening on port ${PORT}`)); 69 | -------------------------------------------------------------------------------- /src/server/utils/dbUtils.js: -------------------------------------------------------------------------------- 1 | const db = require('../models/elephantsql'); 2 | 3 | const dbUtils = {}; 4 | 5 | dbUtils.matchUsernameToID = async (username) => { 6 | const queryString = `SELECT user_id FROM users WHERE username = '${username}'`; 7 | const response = await db.query(queryString); 8 | if (response.rows.length) { 9 | const { userID } = response.rows[0]; 10 | return userID; 11 | } else { 12 | console.error('user id not found when searched by name'); 13 | } 14 | }; 15 | 16 | dbUtils.getIDAndPictureByUsername = async (username) => { 17 | const queryString = `SELECT pic_url, user_id FROM users WHERE username = '${username}'`; 18 | const result = await db.query(queryString); 19 | if (result.rows.length) { 20 | const { user_id, pic_url } = result.rows[0]; 21 | return { user_id, userURL: pic_url }; 22 | } else { 23 | console.error('no name and picture results found'); 24 | } 25 | }; 26 | 27 | dbUtils.saveMessageToDB = async ({ message, user_id }) => { 28 | message = message.replace("'", ''); 29 | console.log(user_id) 30 | const queryString = `INSERT INTO messages (user_id, message) VALUES ('${user_id}', '${message}') `; 31 | 32 | return await db.query(queryString); 33 | }; 34 | 35 | module.exports = dbUtils; 36 | -------------------------------------------------------------------------------- /src/server/ws/ws.js: -------------------------------------------------------------------------------- 1 | const { getIDAndPictureByUsername, saveMessageToDB } = require('../utils/dbUtils'); 2 | const moment = require('moment'); 3 | const redis = require('../redis/redis'); 4 | moment().format(); 5 | 6 | module.exports = (http) => { 7 | const io = require('socket.io')(http); 8 | 9 | let userCount = 0 10 | let userList = [] 11 | 12 | io.on('connect', (socket) => { 13 | console.log('a user connected'); 14 | 15 | 16 | socket.on('message', async ({ message, username }) => { 17 | const { user_id, userURL } = await getIDAndPictureByUsername(username); 18 | const timestamp = moment(); 19 | // creates a message for broadcast to all open sockets 20 | const newMessage = { message: message, username, userURL, timestamp }; 21 | // creates a message formatted for database storage 22 | const dbMessage = { user_id, message }; 23 | try { 24 | // 25 | await saveMessageToDB(dbMessage); 26 | // send the message to all open sockets 27 | io.emit('newMessage', newMessage); 28 | } catch (err) { 29 | console.error(err); 30 | } 31 | }); 32 | 33 | socket.on('signin', ({ username }) => { 34 | userCount++ 35 | console.log(userCount) 36 | io.emit('count', userCount) 37 | 38 | userList.push(username) 39 | io.emit('userlist', userList) 40 | // assigns the anon username to the socketID 41 | redis.set(socket.id.toString(), username); 42 | // console.log(socket.id.toString(),'socketid',username2,'username') 43 | 44 | // claims the anon username as in-use 45 | redis.set(username, 'true'); 46 | 47 | redis.get(username, (data) => { 48 | console.log(data) 49 | }) 50 | 51 | redis.keys('*', (err, data) => { 52 | console.log(data.length, data) 53 | socket.emit('data', data) 54 | }) 55 | 56 | }); 57 | 58 | socket.on('disconnect', () => { 59 | 60 | 61 | 62 | // retrieves the username attached to the socketID on disconnect 63 | redis.get(socket.id.toString(), (err, username) => { 64 | if (err) return console.error(err); 65 | console.log(username,'testing') 66 | console.log(username, 'disconnected'); 67 | 68 | if (!username) return; 69 | //frees the username and socketID from the in-use storage 70 | 71 | 72 | redis.del(socket.id.toString()); 73 | redis.del(username); 74 | 75 | userCount-- 76 | io.emit('count', userCount) 77 | userList = userList.filter((user) => { 78 | console.log(user, username) 79 | return user !== username 80 | }) 81 | redis.keys('*', (err, data) => { 82 | console.log(data.length, data) 83 | }) 84 | console.log(userList) 85 | io.emit('userlist', userList) 86 | }); 87 | }); 88 | }); 89 | return io; 90 | }; 91 | 92 | /* messageObject = { 93 | message: String, 94 | username: String, 95 | userURL: String, 96 | timestamp: String 97 | } 98 | */ 99 | 100 | /* 101 | sendMessage = { 102 | username: String, 103 | message: String, 104 | } 105 | */ -------------------------------------------------------------------------------- /utils/createTables.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | // was causing authentication errors with the connectionString for some reason 4 | // needed the information added explicitly 5 | const db = new Pool({ 6 | user: 'sxxfhsmp', 7 | password: '24uEPIYvk4WgTARJKU3CWztooRTxxlNV', 8 | host: 'ruby.db.elephantsql.com', 9 | database: 'sxxfhsmp', 10 | port: 5432, 11 | }); 12 | 13 | const createUsersTableQuery = `CREATE TABLE users 14 | ( 15 | user_id SERIAL PRIMARY KEY, 16 | username VARCHAR, 17 | pic_url VARCHAR 18 | )`; 19 | 20 | const createMessagesTableQuery = `CREATE TABLE messages 21 | ( 22 | id SERIAL PRIMARY KEY, 23 | timestamp DATE, 24 | user_id INTEGER REFERENCES users(user_id), 25 | message VARCHAR 26 | )`; 27 | 28 | db.query(createUsersTableQuery); 29 | db.query(createMessagesTableQuery); 30 | 31 | /*check which tables exist in the db with: 32 | SELECT table_name 33 | FROM information_schema.tables 34 | WHERE table_schema='public' 35 | AND table_type='BASE TABLE'; 36 | */ -------------------------------------------------------------------------------- /utils/dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CatSnake19/CSAnon/2a8651084271b62d1b93bc20f02eeda6fc68a193/utils/dump.rdb -------------------------------------------------------------------------------- /utils/hashAllUsersUtil.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const bcrypt = require('bcrypt'); 3 | 4 | // was causing authentication errors with the connectionString for some reason 5 | // needed the information added explicitly 6 | 7 | // const connectionString = 'postgres://sxxfhsmp:24uEPIYvk4WgTARJKU3CWztooRTxxlNV@ruby.db.elephantsql.com:5432/sxxfhsmp'; 8 | 9 | const db = new Pool({ 10 | // user: , 11 | // password: , 12 | // host: , 13 | // database: , 14 | // port: , 15 | 16 | // user: 'rdijnfia', 17 | // password: 'kOCId0HmwNJ8mOSTy6gk4Ij8a4nAAFz1', 18 | // host: 'ruby.db.elephantsql.com', 19 | // database: 'rdijnfia', 20 | // port: 5432, 21 | 22 | user: 'sxxfhsmp', 23 | password: '24uEPIYvk4WgTARJKU3CWztooRTxxlNV', 24 | host: 'ruby.db.elephantsql.com', 25 | port: 5432, 26 | 27 | // connectionString: connectionString, 28 | }); 29 | 30 | /* 31 | STEP ONE: 32 | - Get all users from whitelist 33 | 34 | STEP TWO: 35 | - Hash all usernames 36 | 37 | STEP THREE: 38 | - Push each hashed username into hash table 39 | */ 40 | 41 | // STEP ONE 42 | const getAllUsers = async () => { 43 | // This was a connection to a plaintext whitelist 44 | // recommend loading and parsing a local csv 45 | }; 46 | 47 | const SALT_ROUNDS = 8; 48 | 49 | // STEP TWO 50 | const hashUser = async (username) => { 51 | const hash = await bcrypt.hash(username, SALT_ROUNDS); 52 | return hash; 53 | }; 54 | 55 | // STEP THREE 56 | const insertUserIntoDB = async (hashedUser) => { 57 | try { 58 | const queryString = `INSERT INTO hash_table (bcrypt_hash) VALUES ('${hashedUser}')`; 59 | return await db.query(queryString); 60 | } catch (err) { 61 | console.error(err); 62 | } 63 | }; 64 | 65 | // PUTTING IT ALL TOGETHER 66 | 67 | const allTogether = async () => { 68 | let waiting = '.'; 69 | // get all the users, save all the whitelist users in an array 70 | const usersArray = await getAllUsers(); 71 | // loop through the array 72 | for (let i = 0; i < usersArray.length; i++) { 73 | // get the github handle of the user at the index we're looping through 74 | const { github_handle } = usersArray[i]; 75 | // hash the username of that github handle 76 | const hashedUsername = await hashUser(github_handle); 77 | // take the hashed username, and insert it into the database 78 | // await action in order to slow the db calls and prevent throttling errors 79 | await insertUserIntoDB(hashedUsername); 80 | console.log(waiting); 81 | waiting += '.'; 82 | } 83 | }; 84 | 85 | allTogether(); 86 | 87 | /* 88 | 89 | RESPONSE = [ 90 | {github_handle: value} 91 | ] 92 | 93 | TABLE hash_table = { 94 | COLUMN bcrypt_hash varchar UNIQUE 95 | } 96 | 97 | TABLE whitelist = { 98 | COLUMN github_handle varchar 99 | } 100 | 101 | */ 102 | -------------------------------------------------------------------------------- /utils/hashMessages.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | // was causing authentication errors with the connectionString for some reason 4 | // needed the information added explicitly 5 | const db = new Pool({ 6 | user: 'sxxfhsmp', 7 | password: '24uEPIYvk4WgTARJKU3CWztooRTxxlNV', 8 | host: 'ruby.db.elephantsql.com', 9 | database: 'sxxfhsmp', 10 | port: 5432, 11 | }); 12 | 13 | const createUsersTableQuery = `CREATE TABLE users 14 | ( 15 | user_id SERIAL PRIMARY KEY, 16 | username VARCHAR(3000), 17 | pic_url VARCHAR(3000) 18 | )`; 19 | 20 | const createMessagesTableQuery = `CREATE TABLE messages 21 | ( 22 | id SERIAL PRIMARY KEY, 23 | timestamp DATE, 24 | user_id INTEGER REFERENCES users(user_id), 25 | message VARCHAR(3000) 26 | )`; 27 | 28 | //db.query(createUsersTableQuery); 29 | db.query(createMessagesTableQuery); -------------------------------------------------------------------------------- /utils/populateUserDB.js: -------------------------------------------------------------------------------- 1 | const request = require('superagent'); 2 | const { Pool } = require('pg'); 3 | //const db = require('../src/server/models/elephantsql.js'); 4 | 5 | // was causing authentication errors with the connectionString for some reason 6 | // needed the information added explicitly 7 | const connectionString = 'postgres://sxxfhsmp:24uEPIYvk4WgTARJKU3CWztooRTxxlNV@ruby.db.elephantsql.com:5432/sxxfhsmp'; 8 | 9 | const db = new Pool({ 10 | // user: , 11 | // password: , 12 | // host: , 13 | // database: , 14 | // port: , 15 | 16 | // user: 'rdijnfia', 17 | // password: 'kOCId0HmwNJ8mOSTy6gk4Ij8a4nAAFz1', 18 | // host: 'ruby.db.elephantsql.com', 19 | // database: 'rdijnfia', 20 | // port: 5432, 21 | 22 | // user: 'sxxfhsmp', 23 | // password: '24uEPIYvk4WgTARJKU3CWztooRTxxlNV', 24 | // host: 'ruby.db.elephantsql.com', 25 | // port: 5432, 26 | connectionString: connectionString, 27 | }); 28 | 29 | const baseUrl = 'https://pokeapi.co/api/v2/'; 30 | let currentID = 1; 31 | 32 | request 33 | .get(`${baseUrl}pokemon?limit=1000`) 34 | .set('Accept', 'application/json') 35 | .then(async (res) => { 36 | res.body.results.map((pokemon) => { 37 | // THERE'S A WEIRD JUMP FROM 807 TO 10001 38 | if (currentID === 808) currentID = 10001; 39 | pokemon.sprite = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${currentID}.png`; 40 | currentID++; 41 | return pokemon; 42 | }); 43 | 44 | 45 | const pokemonList = res.body.results; 46 | let stars = '*' 47 | 48 | for (let i = 0; i < pokemonList.length; i++) { 49 | if (i % 25 === 0) console.log(stars += "*") 50 | const { sprite, name } = pokemonList[i]; 51 | const queryString = `INSERT INTO users (pic_url, username) VALUES ('${sprite}', '${name}')`; 52 | await db.query(queryString); 53 | } 54 | console.log('Pokemon added to database.'); 55 | }); 56 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | console.log('webpack mode:', process.env.NODE_ENV); 4 | 5 | module.exports = { 6 | mode: process.env.NODE_ENV, 7 | entry: './src/client/index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'bundle.js', 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.jsx?/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | presets: ['@babel/preset-env', '@babel/preset-react'], 21 | }, 22 | }, 23 | }, 24 | { 25 | test: /\.s?css/, 26 | exclude: /node_modules/, 27 | use: ['style-loader', 'css-loader', 'sass-loader'], 28 | }, 29 | ], 30 | }, 31 | devServer: { 32 | // hot:true, 33 | publicPath: '/dist/', 34 | proxy: { 35 | '/': 'http://localhost:3000', 36 | }, 37 | hot: true, 38 | }, 39 | }; 40 | --------------------------------------------------------------------------------