├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------