├── .eslintrc.js ├── .gitignore ├── Readme.md ├── client ├── App.jsx ├── components │ ├── AssignmentCard.jsx │ ├── StudentClassroom.jsx │ ├── TeacherClassroom.jsx │ ├── assignment.jsx │ ├── login.jsx │ ├── signup.jsx │ └── student.jsx ├── index.html ├── index.js └── scss │ ├── _classroom.scss │ ├── _signin.scss │ ├── _variables.scss │ └── application.scss ├── package.json ├── server ├── controllers │ ├── cookieController.js │ ├── sessionController.js │ └── userController.js ├── models │ ├── JSClassroomModels.js │ ├── accountModel.js │ ├── projectModel.js │ └── sessionModel.js ├── routes │ ├── assignments.js │ ├── auth.js │ ├── classroom.js │ ├── login.js │ └── signup.js └── server.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | es2021: true, 6 | }, 7 | extends: [ 8 | 'plugin:react/recommended', 9 | 'airbnb', 10 | ], 11 | overrides: [ 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | sourceType: 'module', 16 | }, 17 | plugins: [ 18 | 'react', 19 | ], 20 | rules: { 21 | 'no-console': 'off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | node_modules/ 3 | build/*.js 4 | package-lock.json 5 | .DS_Store 6 | .env -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Background and Movivation: 2 | 3 | As computer science education continues to gain traction in K-12, more students are outgrowing the constraints of block-based learning platforms like Scratch. Unfortuantely, learning platforms for JavaScript rarely suit the needs of schools - most are either to rigidly structured or self-paced to suit a classroom setting, and those few that are geared towards classroom environments charge more per student annually than most public schools can afford. 4 | 5 | The ultimate goal of this project is to create an open-source JavaScript learning platform that combines some of the classroom and assignment features of Google Classroom with a browser-based IDE in which students can learn to code using standard JavaScript, or a modified, beginner-friendly version focused on visual feedback for earlier learners. 6 | 7 | Educators will be empowered to define the scope of their own assignments and projects and design their own curriculum, while also having access to our own course outlines and a "cookbook" of different projects to choose from to support their growth as computer science educators. 8 | 9 | ## Coming Soon: 10 | - Google OAuth support 11 | - Class rosters 12 | - Multi-class support for teachers 13 | - Improved assignment editing 14 | - JavaScript playground assignment integration 15 | - "Beginner" mode for visual coding with cooridnate plane 16 | 17 | ## Current Tech Stack: 18 | - React 19 | - Node 20 | - Express 21 | - PostgreSQL 22 | - MongoDB -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, useState } from 'react'; 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | 4 | import Login from './components/login'; 5 | import TeacherClassroom from './components/TeacherClassroom'; 6 | import StudentClassroom from './components/StudentClassroom'; 7 | import Signup from './components/signup'; 8 | 9 | function App(props) { 10 | const [role, setRole] = useState(); 11 | return ( 12 | <> 13 | {/*
14 |

Welcome to JavaScript Classroom!

15 |
*/} 16 | {/* { splash === 'login' && } 17 | { splash === 'signup' && } */} 18 |
19 | 20 | } 23 | /> 24 | } 27 | /> 28 | } 31 | /> 32 | } 35 | /> 36 | 37 |
38 | 39 | ); 40 | } 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /client/components/AssignmentCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link, Navigate, useLocation, useNavigate } from 'react-router-dom'; 4 | 5 | export default function AssignmentCard(props) { 6 | const handleOpen = (e) => { 7 | e.preventDefault(); 8 | console.log(props.role); 9 | if(props.role === 'student') { 10 | console.log(props.id) 11 | props.setAssignId(props.id); 12 | props.setAssignName(props.name); 13 | props.setAssignOpen(props.role); 14 | props.setInitialBody(props.body); 15 | } else if (props.role === 'teacher') { 16 | console.log('still in beta'); 17 | } 18 | }; 19 | 20 | return ( 21 | <> 22 | {!props.assignOpen && ( 23 | <> 24 |

{props.name}

25 |

{props.desc}

26 |

Due: {props.due}

27 | 28 | 29 | )} 30 | {/* {props.assignOpen && ( 31 | <> 32 |
33 |

Assignment: {props.name}

34 |
35 |
36 |

Pretend this is a code editor!

37 | 38 |
39 | 40 | )} */} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/components/StudentClassroom.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link, Navigate, useLocation, useNavigate } from 'react-router-dom'; 4 | import AssignmentCard from './AssignmentCard'; 5 | 6 | export default function Classroom(props) { 7 | // const [token, setToken] = useState(); 8 | // const [role, setRole] = useState(); 9 | const role = 'student'; 10 | const [assignments, setAssignments] = useState(0); 11 | const [assignmentElems, setAssignmentElems] = useState([]); 12 | const [assignOpen, setAssignOpen] = useState(null); 13 | const [assignName, setAssignName] = useState(); 14 | const [assignId, setAssignId] = useState(); 15 | const [assignBody, setAssignBody] = useState(''); 16 | const [initialBody, setInitialBody] = useState(''); 17 | console.log('assignId is: ', assignId); 18 | 19 | const aElems = assignmentElems.map((assign) => ( 20 |
21 | 35 |
36 | )); 37 | console.log('You have this many assigments: ', aElems.length); 38 | 39 | const handleAssignSubmit = (e) => { 40 | e.preventDefault(); 41 | console.log('assign Body is ', assignBody); 42 | console.log('assignId is ', assignId); 43 | const assign = { 44 | id: assignId, 45 | name: assignName, 46 | main: assignBody, 47 | }; 48 | console.log('assign sent is ', assign); 49 | fetch('api/assignments', { 50 | method: 'PUT', 51 | body: JSON.stringify(assign), 52 | headers: { 53 | 'Content-Type': 'application/json', 54 | }, 55 | }) 56 | .then((data) => { 57 | console.log('assignment has been saved'); 58 | setAssignOpen(false); 59 | }); 60 | }; 61 | 62 | const navigate = useNavigate(); 63 | 64 | useEffect(() => { 65 | fetch('api/auth', { 66 | method: 'GET', 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | }, 70 | }) 71 | .then((response) => response.json()) 72 | .then((response) => { 73 | console.log('in auth promise response handler with ', response.auth); 74 | if (response.auth === false) navigate('/'); 75 | }) 76 | .catch((err) => { 77 | console.log(`error in token auth: ${err}`); 78 | }); 79 | console.log('useEffect says assignment # is: ', aElems.length); 80 | }); 81 | 82 | useEffect(() => { 83 | fetch('api/assignments') 84 | .then((data) => data.json()) 85 | .then((data) => { 86 | // if (assignments !== data.length) setAssignments(data.length); 87 | console.log('assignments are: ', data.assignments); 88 | setAssignmentElems(data.assignments); 89 | }) 90 | .catch((err) => console.log(`error in fetching assignments ${err}`)); 91 | }, [assignments]); 92 | 93 | return ( 94 | <> 95 | {!assignOpen 96 | && ( 97 | <> 98 |
99 |

Classroom for Students

100 |
101 |
102 | {aElems} 103 |
104 | 105 | 108 | 109 |
110 | 111 | 114 | 115 | 116 | )} 117 | {assignOpen 118 | && ( 119 | <> 120 |
121 |

Assignment: {assignName}

122 |
123 |
124 |

Pretend this is a code editor!

125 | 126 | 127 |
128 | 129 | )} 130 | 131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /client/components/TeacherClassroom.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link, Navigate, useLocation, useNavigate } from 'react-router-dom'; 4 | import AssignmentCard from './AssignmentCard'; 5 | 6 | export default function Classroom(props) { 7 | // const [token, setToken] = useState(); 8 | // const [role, setRole] = useState(); 9 | const role = 'teacher'; 10 | const [assignments, setAssignments] = useState(0); 11 | const [assignmentElems, setAssignmentElems] = useState([]); 12 | const [adding, setAdding] = useState(false); 13 | const [name, setName] = useState(''); 14 | const [desc, setDesc] = useState(''); 15 | const [due, setDue] = useState(''); 16 | const handleEdit = (e) => { 17 | e.preventDefault(); 18 | }; 19 | const aElems = assignmentElems.map((assign, i) => ( 20 |
21 | 28 | 29 |
30 | )); 31 | console.log('You have this many assigments: ', aElems.length); 32 | 33 | const navigate = useNavigate(); 34 | 35 | useEffect(() => { 36 | fetch('api/auth', { 37 | method: 'GET', 38 | headers: { 39 | 'Content-Type': 'application/json', 40 | }, 41 | }) 42 | .then((response) => response.json()) 43 | .then((response) => { 44 | console.log('in auth promise response handler with ', response.auth); 45 | if (response.auth === false) navigate('/'); 46 | }) 47 | .catch((err) => { 48 | console.log(`error in token auth: ${err}`); 49 | }); 50 | console.log('useEffect says assignment # is: ', aElems.length); 51 | }); 52 | 53 | useEffect(() => { 54 | fetch('api/assignments') 55 | .then((data) => data.json()) 56 | .then((data) => { 57 | // if (assignments !== data.length) setAssignments(data.length); 58 | console.log(data); 59 | setAssignmentElems(data.assignments); 60 | }) 61 | .catch((err) => console.log(`error in fetching assignments ${err}`)); 62 | }, [assignments]); 63 | 64 | const handleAdd = () => { 65 | setAdding(true); 66 | }; 67 | const addDone = () => { 68 | setAdding(false); 69 | }; 70 | const handleSubmit = (e) => { 71 | e.preventDefault(); 72 | const assign = { 73 | name, 74 | desc, 75 | due, 76 | }; 77 | fetch('api/assignments', { 78 | method: 'POST', 79 | body: JSON.stringify(assign), 80 | headers: { 81 | 'Content-Type': 'application/json', 82 | }, 83 | }) 84 | .then((response) => { 85 | console.log('in addAssign promise response handler with ', response.status); 86 | if (response.status === 200) { 87 | setAssignments(assignments + 1); 88 | setAdding(false); 89 | } 90 | }) 91 | .catch((err) => { 92 | console.log(`error in assignment submit: ${err}`); 93 | }); 94 | }; 95 | 96 | return ( 97 | <> 98 |
99 |

Classroom for Teachers

100 |
101 |
102 | 103 | {adding && ( 104 |
105 |
106 | 111 |
112 |
113 | 118 |
119 |
120 | 125 |
126 |
127 | 130 |
131 |
132 | 133 |
134 |
135 | )} 136 |
137 | {aElems} 138 |
139 |
140 | 141 | 144 | 145 |
146 | 147 | 150 | 151 |
152 | 153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /client/components/assignment.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; -------------------------------------------------------------------------------- /client/components/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link, useNavigate } from 'react-router-dom'; 4 | import Classroom from './TeacherClassroom'; 5 | 6 | export default function Login(props) { 7 | const [role, setRole] = useState('student'); 8 | const [username, setUsername] = useState(''); 9 | const [password, setPassword] = useState(''); 10 | // const [loggedIn, setLoggedIn] = useState(false); 11 | // const [history, setHistory] = useState(); 12 | // const [history, setHistory] = useState(''); 13 | const navigate = useNavigate(); 14 | const handleSubmit = (e) => { 15 | e.preventDefault(); 16 | const user = { 17 | role, 18 | username, 19 | password, 20 | }; 21 | fetch('api/login', { 22 | method: 'POST', 23 | body: JSON.stringify(user), 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | }, 27 | }) 28 | .then((response) => { 29 | console.log('in login promise response handler with ', response.status); 30 | if (response.status === 200 && role === 'teacher') navigate('/app/teacher'); 31 | if (response.status === 200 && role === 'student') navigate('/app/student'); 32 | }) 33 | .catch((err) => { 34 | console.log(`error in login submit: ${err}`); 35 | }); 36 | }; 37 | 38 | return ( 39 |
40 |
41 |

Welcome to JavaScript Classroom!

42 |

Log in to your account

43 |
44 |
45 |
46 |
47 | 55 |
56 | 61 |
62 |
63 | 68 |
69 |
70 | 73 |
74 |
75 |

Don't have an account?

76 | 77 |
78 | 81 |
82 | 83 |
84 |
85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /client/components/signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link, useNavigate } from 'react-router-dom'; 4 | // const cookieId = Cookies.get('ssid'); 5 | // const user_id = cookieId.slice(3,cookieId.length - 1); 6 | 7 | export default function Signup(props) { 8 | const [role, setRole] = useState('student'); 9 | const [username, setUsername] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | const [fullName, setFullName] = useState(''); 12 | const [email, setEmail] = useState(''); 13 | const navigate = useNavigate(); 14 | const handleSubmit = async (e) => { 15 | e.preventDefault(); 16 | const user = { 17 | role, 18 | username, 19 | password, 20 | email, 21 | fullName, 22 | }; 23 | console.log(user); 24 | fetch('api/signup', { 25 | method: 'POST', 26 | body: JSON.stringify(user), 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | }) 31 | .then((response) => { 32 | console.log('in signup promise response handler with ', response.status); 33 | if (response.status === 200) navigate('/login'); 34 | else console.log('failed to sign up'); 35 | }) 36 | .catch((err) => { 37 | console.log(`error in signup submit: ${err}`); 38 | }); 39 | }; 40 | return ( 41 |
42 |
43 |

Welcome to JavaScript Classroom!

44 |

Sign up

45 |
46 |
47 |
48 |
49 | 57 |
58 | 63 |
64 | 69 |
70 | 75 |
76 | 81 |
82 | 85 |
86 |
87 |

Already Have An Account?

88 | 89 | 92 | 93 |
94 |
95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /client/components/student.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { render } from 'react-dom'; -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JavaScript Classroom 8 | 9 | 10 |
11 |
12 |
13 | 14 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-filename-extension */ 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import App from './App'; 6 | 7 | import styles from './scss/application.scss'; 8 | 9 | render( 10 | 11 | 12 | , 13 | document.getElementById('app'), 14 | ); 15 | -------------------------------------------------------------------------------- /client/scss/_classroom.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidMNorman/Javascript-Classroom/9ea3c509adcd2423e6495825dce0658889de5ebd/client/scss/_classroom.scss -------------------------------------------------------------------------------- /client/scss/_signin.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: $paleTan; 3 | color: $darkGray; 4 | // text-align: center; 5 | // display: flex; 6 | // flex-direction: column; 7 | // align-items: center; 8 | // justify-content: center; 9 | text-align: center; 10 | font-family: Georgia, 'Times New Roman', Times, serif 11 | } 12 | input { 13 | font-size: 17px; 14 | } 15 | main { 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | .assignment-container { 22 | display: flex; 23 | flex-direction: row; 24 | justify-content: center; 25 | align-items: center; 26 | 27 | } 28 | .assignment-card { 29 | margin: 5px solid black; 30 | border: 1px solid black; 31 | background: $lightGreen; 32 | padding: 5px; 33 | 34 | } 35 | #login, h1, h2, p, .button-container, .signup, button { 36 | text-align: center; 37 | display: flex; 38 | flex-direction: column; 39 | align-items: center; 40 | justify-content: center; 41 | text-align: center; 42 | margin: auto; 43 | } 44 | // 45 | // 46 | 47 | button, select, .submit { 48 | background-color: #36A9AE; 49 | background-image: linear-gradient(#37ADB2, #329CA0); 50 | border: 1px solid #2A8387; 51 | border-radius: 4px; 52 | box-shadow: rgba(0, 0, 0, 0.12) 0 1px 1px; 53 | color: #FFFFFF; 54 | cursor: pointer; 55 | display: block; 56 | font-family: -apple-system,".SFNSDisplay-Regular","Helvetica Neue",Helvetica,Arial,sans-serif; 57 | font-size: 17px; 58 | line-height: 100%; 59 | margin: 0; 60 | outline: 0; 61 | padding: 11px 15px 12px; 62 | text-align: center; 63 | transition: box-shadow .05s ease-in-out,opacity .05s ease-in-out; 64 | user-select: none; 65 | -webkit-user-select: none; 66 | touch-action: manipulation; 67 | width: 100%; 68 | } 69 | 70 | button:hover { 71 | box-shadow: rgba(255, 255, 255, 0.3) 0 0 2px inset, rgba(0, 0, 0, 0.4) 0 1px 2px; 72 | text-decoration: none; 73 | transition-duration: .15s, .15s; 74 | } 75 | 76 | button:active { 77 | box-shadow: rgba(0, 0, 0, 0.15) 0 2px 4px inset, rgba(0, 0, 0, 0.4) 0 1px 1px; 78 | } 79 | 80 | button:disabled { 81 | cursor: not-allowed; 82 | opacity: .6; 83 | } 84 | 85 | button:disabled:active { 86 | pointer-events: none; 87 | } 88 | 89 | button:disabled:hover { 90 | box-shadow: none; 91 | } 92 | 93 | .logout { 94 | background-color: #a0afb0; 95 | background-image: linear-gradient(#ceebec, #e6f6f7); 96 | border: 1px solid #2A8387; 97 | color: #2A8387; 98 | border-radius: 4px; 99 | box-shadow: rgba(0, 0, 0, 0.12) 0 1px 1px; 100 | } 101 | -------------------------------------------------------------------------------- /client/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $paleAqua: #8adcc5; 2 | $paleTan: #fefae0; 3 | $lightBrown: #faedcd; 4 | $brown: #d4a373; 5 | $lightGreen: #e9edc9; 6 | $green: #ccd5ae; 7 | $darkGray: #353535; -------------------------------------------------------------------------------- /client/scss/application.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | @import '_classroom'; 3 | @import '_signin'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-classroom", 3 | "version": "1.0.0", 4 | "description": "a digital classroom platform for k-12 JavaScript learning", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production node server/server.js", 8 | "build": "webpack", 9 | "dev": "NODE_ENV=development webpack server --open --hot --progress --color & nodemon server/server.js" 10 | }, 11 | "nodemonConfig": { 12 | "ignore": [ 13 | "build", 14 | "client" 15 | ] 16 | }, 17 | "author": "David Norman", 18 | "license": "ISC", 19 | "dependencies": { 20 | "bcrypt": "^5.1.0", 21 | "cirrus-ui": "^0.7.1", 22 | "cookie-parser": "^1.4.6", 23 | "express": "^4.18.2", 24 | "express-session": "^1.17.3", 25 | "mongoose": "^7.0.0", 26 | "pg": "^8.9.0", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-router-dom": "^6.8.2" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.21.0", 33 | "@babel/preset-env": "^7.20.2", 34 | "@babel/preset-react": "^7.18.6", 35 | "babel-loader": "^9.1.2", 36 | "css-loader": "^6.7.3", 37 | "dotenv": "^16.0.3", 38 | "eslint": "^8.35.0", 39 | "eslint-config-airbnb": "^19.0.4", 40 | "eslint-plugin-import": "^2.27.5", 41 | "eslint-plugin-jsx-a11y": "^6.7.1", 42 | "eslint-plugin-react": "^7.32.2", 43 | "eslint-plugin-react-hooks": "^4.6.0", 44 | "html-webpack-plugin": "^5.5.0", 45 | "mini-css-extract-plugin": "^2.7.2", 46 | "nodemon": "^2.0.20", 47 | "sass": "^1.58.3", 48 | "sass-loader": "^13.2.0", 49 | "webpack": "^5.75.0", 50 | "webpack-cli": "^5.0.1", 51 | "webpack-dev-server": "^4.11.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | const cookieController = {}; 2 | 3 | // cookieController.setCookie = (req, res, next) => { 4 | // return next(); 5 | // }; 6 | 7 | const cyrb53 = (str, seed = 0) => { 8 | let h1 = 0xdeadbeef ^ seed; 9 | let h2 = 0x41c6ce57 ^ seed; 10 | for (let i = 0, ch; i < str.length; i++) { 11 | ch = str.charCodeAt(i); 12 | h1 = Math.imul(h1 ^ ch, 2654435761); 13 | h2 = Math.imul(h2 ^ ch, 1597334677); 14 | } 15 | 16 | h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); 17 | h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); 18 | 19 | return 4294967296 * (2097151 & h2) + (h1 >>> 0); 20 | }; 21 | 22 | cookieController.setSSIDCookie = (req, res, next) => { 23 | console.log('in setSSIDCookie'); 24 | console.log('req.cookies.SSID before setting ', req.cookies.SSID); 25 | console.log('res.locals.id is ', res.locals.id); 26 | const cookieID = cyrb53(res.locals.id); 27 | console.log('cookieID is ', cookieID); 28 | res.locals.cookieID = cookieID; 29 | console.log('res.locals.coookieID in setCookie: ', res.locals.cookieID); 30 | if (req.cookies.SSID !== cookieID) res.cookie('SSID', `${cookieID}`, { httpOnly: true }); 31 | return next(); 32 | }; 33 | 34 | module.exports = cookieController; 35 | -------------------------------------------------------------------------------- /server/controllers/sessionController.js: -------------------------------------------------------------------------------- 1 | const models = require('../models/JSClassroomModels'); 2 | 3 | const sessionController = {}; 4 | 5 | sessionController.isLoggedIn = async (req, res, next) => { 6 | try { 7 | // check to see if req.cookies.SSID's ObjectId matches an active session in Session 8 | // console.log('req.cookies.SSID:', res.locals.cookieID); 9 | const active = await models.Session.exists({ cookieId: res.locals.cookieID }); 10 | // console.log('active is, ', active); 11 | res.locals.auth = !!active; 12 | // console.log('session is active?', res.locals.auth); 13 | return next(); 14 | } catch (e) { 15 | return console.log(e); 16 | } 17 | }; 18 | 19 | sessionController.startSession = (req, res, next) => { 20 | // console.log('in sessionController.startSession with cookieID of ', res.locals.cookieID); 21 | // console.log('start session auth value: ', res.locals.auth); 22 | if (res.locals.auth !== true) { 23 | // console.log('creating session'); 24 | models.Session.create({ cookieId: res.locals.cookieID }) 25 | .then((_response) => { 26 | // console.log('Session Started'); 27 | return next(); 28 | }) 29 | .catch((e) => console.log(e)); 30 | } else { 31 | return next(); 32 | } 33 | }; 34 | 35 | module.exports = sessionController; 36 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt'); 2 | const userDb = require('../models/accountModel'); 3 | 4 | const userController = {}; 5 | const SALT_WORK_FACTOR = 10; 6 | 7 | userController.hashPW = async (req, res, next) => { 8 | try { 9 | const hash = await bcrypt.hash(req.body.password, SALT_WORK_FACTOR) 10 | req.body.password = hash; 11 | return next(); 12 | } catch (e) { 13 | console.log(e); 14 | return next({ 15 | log: 'Error in userController in hashPW', 16 | message: { err: 'userController.hashPW: ERROR: Check server logs for details ' }, 17 | }); 18 | } 19 | }; 20 | 21 | userController.addUser = async (req, res, next) => { 22 | const user = req.body; 23 | try { 24 | let createReq = ''; 25 | if (user.role === 'teacher') createReq = 'INSERT INTO teacher (username, password, email, name) VALUES ($1, $2, $3, $4) '; 26 | else createReq = 'INSERT INTO student (username, password, email, name) VALUES ($1, $2, $3, $4) '; 27 | await userDb.query(createReq, [user.username, user.password, user.email, user.fullName]); 28 | // console.log('user created for req.body.username'); 29 | return next(); 30 | } catch (e) { 31 | console.log(e); 32 | return next({ 33 | log: 'Error in userController in addUser', 34 | message: { err: 'userController.addUser: ERROR: Check server logs for details ' }, 35 | }); 36 | } 37 | }; 38 | // BODY SHOULD HAVE TYPE PROPERTY WITH TEACHER OR STUDENT 39 | 40 | userController.verifyUser = async (req, res, next) => { 41 | try { 42 | // console.log('in verify user'); 43 | // console.log(req.body); 44 | const verifyQuery = `SELECT password FROM ${req.body.role} WHERE username = '${req.body.username}'`; 45 | // if (user.role === 'teacher') verifyQuery = 'SELECT password FROM teacher WHERE ' 46 | const user = await userDb.query(verifyQuery); 47 | // console.log('returned user is: ', user.rows[0].password); 48 | // compare returned password with bcrypt compare 49 | res.locals.valid = await bcrypt.compare(req.body.password, user.rows[0].password); 50 | // console.log(res.locals.valid); 51 | return next(); 52 | } catch (e) { 53 | console.log(e); 54 | return next({ 55 | log: 'Error in userController in verifyUser', 56 | message: { err: 'userController.verifyUser: ERROR: Check server logs for details ' }, 57 | }); 58 | } 59 | }; 60 | 61 | userController.getStudents = async (req, res, next) => { 62 | // figure out how to pull class id from body of request - 63 | // teacher should have clicked on a classroom 64 | const classroom = req.body.class_id; 65 | // select only the students for the particular class _id 66 | const students = `SELECT s.name AS student, FROM ` 67 | // query databse with students 68 | 69 | // assign res.locals to the response of the query 70 | }; 71 | 72 | userController.getID = async (req, res, next) => { 73 | try { 74 | // console.log('in getID'); 75 | const idQuery = `SELECT id FROM ${req.body.role} WHERE username = '${req.body.username}'`; 76 | // console.log(`querying: SELECT id FROM ${req.body.role} WHERE username = '${req.body.username}'`); 77 | const id = await userDb.query(idQuery); 78 | // console.log('object returned from query is ', id.rows[0].id); 79 | res.locals.id = `${id.rows[0].id}${req.body.role}`; 80 | return next(); 81 | } catch (e) { 82 | return next({ 83 | log: `There was an error in userController.getID, ${e}`, 84 | }); 85 | } 86 | }; 87 | 88 | module.exports = userController; 89 | -------------------------------------------------------------------------------- /server/models/JSClassroomModels.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { Schema } = mongoose.Schema; 4 | 5 | const sessionSchema = new Schema({ 6 | cookieId: { type: String, required: true, unique: true }, 7 | createdAt: { type: Date, expires: 86400, default: Date.now }, 8 | }); 9 | const Session = mongoose.model('Session', sessionSchema); 10 | 11 | const assignmentSchema = new Schema({ 12 | name: { type: String, required: true, unique: false }, 13 | description: String, 14 | dueDate: String, 15 | body: String, 16 | }); 17 | const Assignment = mongoose.model('Assignment', assignmentSchema); 18 | 19 | module.exports = { 20 | Session, 21 | Assignment, 22 | }; 23 | -------------------------------------------------------------------------------- /server/models/accountModel.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | const { PG_URI } = process.env; 4 | 5 | // create new pool 6 | const pool = new Pool({ 7 | connectionString: PG_URI, 8 | }); 9 | 10 | module.exports = { 11 | query: (text, params, callback) => { 12 | console.log('executing query', text); 13 | return pool.query(text, params, callback); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /server/models/projectModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const { MONGO_URI } = process.env; 4 | 5 | mongoose.connect(MONGO_URI, { 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | dbName: 'projects', 9 | }) 10 | .then(() => console.log('Connected to Mongo DB.')) 11 | .catch((err) => console.log(err)); 12 | 13 | const { Schema } = mongoose; 14 | 15 | const assignment = new Schema ({ 16 | 17 | }) -------------------------------------------------------------------------------- /server/models/sessionModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/routes/assignments.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const sessionController = require('../controllers/sessionController'); 3 | const userController = require('../controllers/userController'); 4 | const models = require('../models/JSClassroomModels'); 5 | 6 | const router = express.Router(); 7 | 8 | router.get( 9 | '/', 10 | async (req, res) => { 11 | const assignments = await models.Assignment.find(); 12 | // console.log(`found ${assignments} from mongodb`); 13 | // console.log('assignment find response type is array? ', Array.isArray(assignments)); 14 | res.locals.assignments = assignments; 15 | return res.status(200).send({ assignments: res.locals.assignments }); 16 | }, 17 | ); 18 | 19 | router.put( 20 | '/', 21 | async (req, res) => { 22 | try { 23 | console.log('in assignment put req with, ', req.body); 24 | const saved = await models.Assignment.findOneAndUpdate( 25 | { _id: req.body.id }, 26 | { body: req.body.main }, 27 | ); 28 | console.log('assignment update complete'); 29 | return res.status(200).send('Assignment saved'); 30 | } catch (e) { 31 | return console.log(`Error ${e} in assignment updator`); 32 | } 33 | }, 34 | ); 35 | 36 | router.post( 37 | '/', 38 | async (req, res) => { 39 | try { 40 | const assign = req.body; 41 | await models.Assignment.create({ 42 | name: assign.name, description: assign.desc, dueDate: assign.due 43 | }); 44 | return res.status(200).send('yeah boiiiiii'); 45 | } catch (e) { 46 | return console.log(`Hey buddy, you got a big e problem in assign posting ${e}`); 47 | } 48 | }, 49 | ); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /server/routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const sessionController = require('../controllers/sessionController'); 3 | const userController = require('../controllers/userController'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get( 8 | '/', 9 | (req, res, next) => { 10 | // console.log('in auth router with req these cookies: ', req.cookies); 11 | res.locals.cookieID = req.cookies.SSID; 12 | return next(); 13 | }, 14 | sessionController.isLoggedIn, 15 | (req, res) => { 16 | const responseObj = { auth: res.locals.auth }; 17 | console.log('auth response should be: ', responseObj); 18 | res.status(200).send(responseObj); 19 | }, 20 | ); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /server/routes/classroom.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | 5 | router.get( 6 | '/', 7 | // authentication middleware 8 | // serve up a response 9 | (req, res) => { 10 | // console.log('passed auth in classroom router'); 11 | res.status(200); 12 | }, 13 | ); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /server/routes/login.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const userController = require('../controllers/userController'); 3 | const cookieController = require('../controllers/cookieController'); 4 | const sessionController = require('../controllers/sessionController'); 5 | 6 | const router = express.Router(); 7 | 8 | router.get( 9 | '/', 10 | // serve up the login page 11 | ); 12 | 13 | router.post( 14 | '/', 15 | userController.verifyUser, 16 | (req, res, next) => { 17 | if (res.locals.valid === false) return res.redirect('/login'); 18 | return next(); 19 | }, 20 | userController.getID, 21 | cookieController.setSSIDCookie, 22 | sessionController.isLoggedIn, 23 | sessionController.startSession, 24 | (req, res) => { 25 | console.log(`${req.body.username} successfully logged in`); 26 | // console.log(res.locals); 27 | return res.status(200).send({ auth: res.locals.auth }); 28 | }, 29 | ); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /server/routes/signup.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const userController = require('../controllers/userController'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get( 8 | '/', 9 | // serve up the signup file 10 | (req, res) => { 11 | res.status(200).sendFile(path.join(__dirname, '../client/index.html')); 12 | }, 13 | ); 14 | 15 | router.post( 16 | '/', 17 | userController.hashPW, 18 | userController.addUser, 19 | (req, res) => { 20 | console.log('user creation complete'); 21 | return res.status(200).redirect('/'); 22 | }, 23 | ); 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const cookieParser = require('cookie-parser'); 4 | const mongoose = require('mongoose'); 5 | 6 | const app = express(); 7 | 8 | // require in api routes 9 | const signupRouter = require('./routes/signup'); 10 | const loginRouter = require('./routes/login'); 11 | const classroomRouter = require('./routes/classroom'); 12 | const authRouter = require('./routes/auth'); 13 | const assignRouter = require('./routes/assignments'); 14 | 15 | const MONGO_URI = "mongodb+srv://jsclassadmin:Ve3HDuMFndXA7inH@js-classroom-db.oy8lwan.mongodb.net/?retryWrites=true&w=majority"; 16 | mongoose.connect(MONGO_URI, { useNewUrlParser: true }) 17 | const mdb = mongoose.connection; 18 | mdb.on('error', (error) => console.error(error)); 19 | mdb.once('open', () => console.log('Connected to Mongoose')); 20 | 21 | // const PORT = process.env.NODE_ENV === 'production' ? 3000 : 8080; 22 | const PORT = 3000; 23 | 24 | // parse request body 25 | app.use(express.json()); 26 | app.use(express.urlencoded({ extended: true })); 27 | app.use(cookieParser()); 28 | 29 | // handle requests for static files 30 | app.use(express.static(path.resolve(__dirname, '../client'))); 31 | app.get( 32 | '/', 33 | (req, res) => { 34 | // console.log('in main url path'); 35 | res.status(200).sendFile(path.join(__dirname, '../client/index.html')); 36 | }, 37 | ); 38 | 39 | // route handlers 40 | app.use('/api/signup', signupRouter); 41 | app.use('/api/login', loginRouter); 42 | app.use('/api/app', classroomRouter); 43 | app.use('/app/api/auth', authRouter); 44 | app.use('/api/auth', authRouter); 45 | app.use('/signup', signupRouter); 46 | app.use('/login', loginRouter); 47 | app.use('/app', classroomRouter); 48 | app.use('/auth', authRouter); 49 | app.use('/app/api/assignments', assignRouter); 50 | app.use('/api/assignments', assignRouter); 51 | 52 | // catch-all route handler 53 | app.use((_, res) => res.status(404).send('Page Not Found')); 54 | 55 | // error handler 56 | app.use((err, req, res, next) => { 57 | const defaultErr = { 58 | log: 'Express error handler caught unknown middleware error', 59 | status: 500, 60 | message: { err: 'An error occurred' }, 61 | }; 62 | const errorObj = { ...defaultErr, ...err }; 63 | console.log(errorObj.log); 64 | return res.status(errorObj.status).json(errorObj.message).redirect('/'); 65 | }); 66 | 67 | // start server 68 | app.listen(PORT, () => { 69 | console.log(`Server listening on port: ${PORT}...`); 70 | }); 71 | 72 | module.exports = app; 73 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | const { NODE_ENV } = process.env; 6 | 7 | module.exports = { 8 | mode: NODE_ENV, 9 | entry: './client/index.js', 10 | 11 | output: { 12 | path: path.resolve(__dirname, './build'), 13 | filename: 'bundle.js', 14 | // publicPath: '/', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /.jsx?/, 20 | exclude: /node_modules/, 21 | loader: 'babel-loader', 22 | options: { 23 | presets: ['@babel/preset-env', '@babel/preset-react'], 24 | }, 25 | }, 26 | { 27 | test: /\.s?css/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', { 29 | loader: 'sass-loader', 30 | options: { 31 | implementation: require('sass'), 32 | }, 33 | }], 34 | }, 35 | ], 36 | }, 37 | resolve: { 38 | extensions: ['.js', '.jsx'], 39 | }, 40 | plugins: [ 41 | new MiniCssExtractPlugin(), 42 | new HTMLWebpackPlugin({ 43 | title: 'Development', 44 | template: path.join(__dirname, './client/index.html'), 45 | }), 46 | ], 47 | devServer: { 48 | static: { 49 | publicPath: '/', 50 | directory: path.join(__dirname, './dist'), 51 | }, 52 | proxy: { 53 | '/api/**': { 54 | target: 'http://localhost:3000/', 55 | secure: false, 56 | pathRewrite: { '^api': '' }, 57 | }, 58 | '/assets/**': { 59 | target: 'http://localhost:3000/', 60 | secure: false, 61 | }, 62 | '/app/**': { 63 | target: 'http://localhost:3000/', 64 | secure: false, 65 | }, 66 | '/auth/**': { 67 | target: 'http://localhost:3000/', 68 | secure: false, 69 | }, 70 | '/app/api/auth': { 71 | target: 'http://localhost:3000/', 72 | secure: false, 73 | }, 74 | }, 75 | hot: true, 76 | compress: true, 77 | port: 8080, 78 | }, 79 | optimization: { 80 | splitChunks: { 81 | chunks: 'async', 82 | minSize: 20000, 83 | minRemainingSize: 0, 84 | minChunks: 1, 85 | maxAsyncRequests: 30, 86 | maxInitialRequests: 30, 87 | enforceSizeThreshold: 50000, 88 | cacheGroups: { 89 | defaultVendors: { 90 | test: /[\\/]node_modules[\\/]/, 91 | priority: -10, 92 | reuseExistingChunk: true, 93 | }, 94 | default: { 95 | minChunks: 2, 96 | priority: -20, 97 | reuseExistingChunk: true, 98 | }, 99 | }, 100 | }, 101 | }, 102 | }; 103 | --------------------------------------------------------------------------------