diff --git a/MilestoneSubmissions/Lab 11 Deliverable Test Plan.docx b/MilestoneSubmissions/Lab 11 Deliverable Test Plan.docx new file mode 100644 index 0000000..35281df Binary files /dev/null and b/MilestoneSubmissions/Lab 11 Deliverable Test Plan.docx differ diff --git a/MilestoneSubmissions/Lab 11 Deliverable Test Plan.pdf b/MilestoneSubmissions/Lab 11 Deliverable Test Plan.pdf new file mode 100644 index 0000000..48724f4 Binary files /dev/null and b/MilestoneSubmissions/Lab 11 Deliverable Test Plan.pdf differ diff --git a/ProjectSourceCode/.env b/ProjectSourceCode/.env new file mode 100644 index 0000000..33d4900 --- /dev/null +++ b/ProjectSourceCode/.env @@ -0,0 +1,7 @@ +# database credentials +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="pwd" +POSTGRES_DB="users_db" + +# Node vars +SESSION_SECRET="super duper secret!" \ No newline at end of file diff --git a/ProjectSourceCode/docker-compose.yaml b/ProjectSourceCode/docker-compose.yaml index c60a4a5..f3947ec 100644 --- a/ProjectSourceCode/docker-compose.yaml +++ b/ProjectSourceCode/docker-compose.yaml @@ -1,6 +1,7 @@ version: '3.9' services: - db: + #these are containers + db: #this is for the database image: postgres:14 #env_file: .env expose: @@ -8,7 +9,7 @@ services: volumes: - group-project:/var/lib/postgresql/data - ./src/init_data:/docker-entrypoint-initdb.d - web: + web: #this is for the website image: node:lts user: 'node' working_dir: /repository @@ -21,6 +22,6 @@ services: - '3000:3000' volumes: - ./:/repository - command: 'npm start' + command: 'npm run start' volumes: group-project: \ No newline at end of file diff --git a/ProjectSourceCode/package.json b/ProjectSourceCode/package.json index 98898d4..489e569 100644 --- a/ProjectSourceCode/package.json +++ b/ProjectSourceCode/package.json @@ -1,18 +1,27 @@ { - "name": "node-ejs", - "main": "index.js", - "dependencies": { - "ejs": "^2.5.5", - "ejs-lint": "^0.3.0", - "body-parser": "^1.20.0", - "express": "^4.6.1", - "express-session": "^1.17.3", - "express-handlebars": "^7.1.2", - "pg-promise": "^10.11.1", - "nodemon": "2.0.20" - }, - "scripts": { - "prestart": "npm install", - "start": "nodemon src/index.js" - } - } \ No newline at end of file + "name": "SoftwareDevTeamProject", + "main": "src/index.js", + "dependencies": { + "express": "^4.6.1", + "pg-promise": "^10.11.1", + "body-parser": "1.20.0", + "express-session": "1.17.3", + "express-handlebars": "^7.1.2", + "handlebars": "^4.7.8", + "axios": "^1.1.3", + "bcryptjs": "^2.4.0" + }, + "devDependencies": { + "nodemon": "^2.0.7", + "mocha": "^6.2.2", + "chai": "^4.2.0", + "chai-http": "^4.3.0", + "npm-run-all": "^4.1.5" + }, + "scripts": { + "prestart": "npm install", + "start": "nodemon index.js", + "test": "mocha", + "testandrun": "npm run prestart && npm run test && npm start" + } +} diff --git a/ProjectSourceCode/src/index.js b/ProjectSourceCode/src/index.js index de628a7..9afa887 100644 --- a/ProjectSourceCode/src/index.js +++ b/ProjectSourceCode/src/index.js @@ -3,105 +3,215 @@ const express = require('express'); const app = express(); const handlebars = require('express-handlebars'); const path = require('path'); -const pgp = require('pg-promise')(); +const pgp = require('pg-promise')(); //library that gives me access to make any database that i have access to normally +const bcrypt = require('bcryptjs'); // To hash passwords const bodyParser = require('body-parser'); const session = require('express-session'); +const { getBuiltinModule } = require('process'); // ------------------------------------- APP CONFIG ---------------------------------------------- // create `ExpressHandlebars` instance and configure the layouts and partials dir. const hbs = handlebars.create({ extname: 'hbs', - layoutsDir: path.join(__dirname, 'views/layouts'), - partialsDir: [ - path.join(__dirname, 'views/partials'), - path.join(__dirname, 'views/partials/svg_components') - ], - helpers: { - range: function(start, end, options) { - let result = ''; - for (let i = start; i < end; i++) { - result += options.fn(i); - } - return result; - } - } + layoutsDir: __dirname + '/views/layouts', + partialsDir:[ + __dirname + '/views/partials', + __dirname + '/views/partials/svg_components' + ] }); +// ------------------------------------- DB CONFIG AND CONNECT --------------------------------------- +//TODO: Use this later for setting up db! +//accessed by either: hosted by another entity (need to have the url to access that) + //hosted outself, create two docker containers that runs the application and the other the database +const dbConfig = { + host: 'db', + port: 5432, + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, +}; +const db = pgp(dbConfig); + + +//// db test +db.connect() + .then(obj => { + // Can check the server version here (pg-promise v10.1.0+): + console.log('Database connection successful'); + obj.done(); // success, release the connection; + }) + .catch(error => { + console.log('ERROR', error.message || error); + }); // Register `hbs` as our view engine using its bound `engine()` function. app.engine('hbs', hbs.engine); app.set('view engine', 'hbs'); app.set('views', path.join(__dirname, 'views')); - -// Static and session configuration app.use(bodyParser.json()); +// OHs this should fix my style.css inaccessibility issue app.use('/resources', express.static(path.join(__dirname, 'resources'))); +// set Session app.use( session({ - secret: "super duper secret!", // Consider moving this to .env as process.env.SESSION_SECRET + secret: "super duper secret!", //TODO: they might want us to put this in an env file, like: process.env.SESSION_SECRET saveUninitialized: true, resave: true, }) ); -app.use(bodyParser.urlencoded({ extended: true })); - -// ------------------------------------- DB CONFIG AND CONNECT --------------------------------------- -const dbConfig = { - host: 'db', - port: 5432, - database: process.env.POSTGRES_DB, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, -}; -const db = pgp(dbConfig); - -// Routes -app.get('/page1', (req, res) => { - res.render('pages/page1'); -}); +app.use( + bodyParser.urlencoded({ + extended: true, + }) +); -app.get('/page2', (req, res) => { - res.render('pages/page2'); +app.get('/welcome', (req, res) => { + res.json({status: 'success', message: 'Welcome!'}); }); app.get('/', (req, res) => { - res.redirect('/login'); + res.redirect('/login'); //this will call the /anotherRoute route in the API }); - + app.get('/login', (req, res) => { res.render('pages/login'); }); -app.get('/scoreboard', (req, res) => { - res.render('pages/scoreboard'); -}); +app.post('/login', async (req, res) => { + // console.log('login post accessed') + const username = req.body.username + const password = req.body.password + const hash = await bcrypt.hash(password, 10); + // console.log("Hashed password:", hash) + const sqlUsername = "SELECT * FROM users WHERE username = $1;" + + try{ + //async + await make it so that I don't need to do .then(data etc..) which makes the code cleaner and work more efficiently. + const user = await db.one(sqlUsername, [username]) + const match = await bcrypt.compare(password, user.password) + + //looks like there's a space for some reason? Why tho...? + // console.log("Username is:", username, ", Other username is:", user.username); + // console.log("Password is: ", password, ", Other password is: ", user.password) + // console.log("Matched as:", match); + // rest is mine from earlier + if(match){ + // if (user.password == password && user.username == username){ + // console.log("if statement") + req.session.user = user; + req.session.save(); + res.status(200); + res.redirect('/page1') + } + + else{ + // console.log("else statement") + // If the password is incorrect, render the login page and send a message to the user stating "Incorrect username or password." + res.render('pages/login', {message:"Incorrect username or password.", error:true}) + // res.render('/login') + } + } + catch{ + console.log("User doesn't exist! Try registering.") + res.redirect('/register') + } + }) -app.get('/inventory', (req, res) => { - res.render('pages/inventory'); +//register +app.get('/register', (req, res) => { + res.render('pages/register'); }); -app.post('/login', async (req, res) => { +//from lab 8 +app.post('/register', async (req, res) => { + //hash the password using bcrypt library + const hash = await bcrypt.hash(req.body.password, 10); + + // DONE: Insert username and hashed password into the 'users' table const username = req.body.username; const password = req.body.password; - const sqlUsername = "SELECT * FROM users WHERE username = $1;"; + // console.log(username, password, hash); + //the rest of the information in the users table is auto generated + const sqlRegister = "INSERT INTO users (username, password) VALUES ($1, $2);" ;//removed returning * - try { - const user = await db.one(sqlUsername, [username]); - const match = await bcrypt.compare(password, user.password); - if (match) { - req.session.user = user; - req.session.save(); - res.redirect('/discover'); - } else { - res.render('pages/login', {message: "Incorrect username or password.", error: true}); - } - } catch { - console.log("User doesn't exist! Try registering"); - res.redirect('/register'); + db.none(sqlRegister, [username, hash]) //changed any to none + /* + Redirect to GET /login route page after data has been inserted successfully. + If the insert fails, redirect to GET /register route. + */ + .then(data => { + // console.log("Registered user with: ", data) + //res.redirect('/login', {message:"Error discovering data.", error:true}) + // res.json({status: 'success'}); + res.status(200).render('pages/register', {message: "Registration Successful!"}); + // res.redirect('/login', {message:"Registration Successful!"}); + // res.redirect('/login'); + }) + .catch(function (err) { + res.status(400).render('pages/register', {message: "Registration Error!", error: true}); + // res.redirect('/register', {message:"Registration Error!", error: true}); + }); +}); + +app.post('/update_item_status', async (req, res) => { + const {item_id, new_status} = req.body; + // console.log("Req.body in post req: ",req.body); + //try to update the item status + try{ + const sql_item_update = 'UPDATE items SET status = $1 WHERE item_id = $2 RETURNING *'; + //call update with db.one and the new_status and item_id that we want to update + db.one(sql_item_update, [new_status, item_id]) + //do we need the data? just put it because I always do. + //also, don't reload the page bc tehre's no need (I think? We dont' want to have to refresh the page everytime we click an item) + .then(data => { + res.status(200).send({message:"Item status updated successfully!"}); + // console.log('Item_id: ', item_id, " and new_status: ", new_status); + }) + //reload the page with the error message pop-up + .catch(function (err) { + res.status(400).json({message:"Item Status Update Error!"}); + // res.redirect('/page2'); + }); + } + //error if unable to + catch (error){ + console.error('Error updating item status: ', error); + res.status(400).send({error: 'Failed to update item status.'}); + } +}) + +// Authentication Middleware. +const auth = (req, res, next) => { + // console.log(req.session) + if (!req.session.user) { + // Default to login page. + return res.redirect('/login'); } + next(); +}; + +// Authentication Required +app.use(auth); + +app.get('/page1', (req, res) => { + res.render('pages/page1'); //this will call the /anotherRoute route in the API +}); + +app.get('/page2', (req, res) => { + res.render('pages/page2'); //this will call the /anotherRoute route in the API }); -// Start the server -app.listen(3000, () => { - console.log('Server is listening on port 3000'); +app.get('/page3', (req, res) => { + res.render('pages/page3'); //this will call the /anotherRoute route in the API }); + + +app.get('/logout', (req, res) => { + req.session.destroy() + res.status(200); + res.render('pages/login', {message:"Logged out successfully!", error:false}) +}); + +module.exports = app.listen(3000); +console.log('Server is listening on port 3000'); \ No newline at end of file diff --git a/ProjectSourceCode/src/init_data/create.sql b/ProjectSourceCode/src/init_data/create.sql index 645d1ed..c192b12 100644 --- a/ProjectSourceCode/src/init_data/create.sql +++ b/ProjectSourceCode/src/init_data/create.sql @@ -1,31 +1,26 @@ -<<<<<<< HEAD -CREATE TABLE players ( - id SERIAL PRIMARY KEY, - username VARCHAR(255) NOT NULL, - score INT NOT NULL -======= CREATE TABLE users ( - user_id SERIAL NOT NULL PRIMARY KEY, - username VARCHAR(30) NOT NULL, - password VARCHAR(30) NOT NULL, + user_id SERIAL PRIMARY KEY, + username VARCHAR(30) NOT NULL UNIQUE, + password CHAR(60) NOT NULL, timer TIME DEFAULT '00:00:00', - progress TINYINT DEFAULT '0' + progress INT DEFAULT '0' ); CREATE TABLE puzzles ( - puzzle_id SERIAL NOT NULL PRIMARY KEY, - value TINYINT NOT NULL + puzzle_id SERIAL PRIMARY KEY, + name VARCHAR(30) NOT NULL, + value INT NOT NULL ); CREATE TABLE users_puzzles ( - user_id FOREIGN KEY (user_id) REFERENCES users(user_id), - puzzle_id FOREIGN KEY (puzzle_id) REFERENCES puzzles(puzzle_id), - is_solved BOOL DEFAULT '0', + user_id INT REFERENCES users(user_id) ON DELETE CASCADE, + puzzle_id INT REFERENCES puzzles(puzzle_id) ON DELETE CASCADE, + is_solved BOOLEAN DEFAULT FALSE, PRIMARY KEY (user_id, puzzle_id) ); CREATE TABLE items ( item_id SERIAL PRIMARY KEY, + name VARCHAR(30) NOT NULL, status VARCHAR(10) CHECK(status IN('unknown','found','active','disabled')) ->>>>>>> origin/main ); \ No newline at end of file diff --git a/ProjectSourceCode/src/init_data/insert.sql b/ProjectSourceCode/src/init_data/insert.sql index f0c6d80..d774383 100644 --- a/ProjectSourceCode/src/init_data/insert.sql +++ b/ProjectSourceCode/src/init_data/insert.sql @@ -3,43 +3,33 @@ -- 1-3 users with username and passwords, default timer and progress INSERT INTO users (username, password) -VALUES ( - ('test', '123'), - ('admin', 'admin') - ('beep', 'boop') -); +VALUES + -- username: admin, password: admin + ('admin','$2a$10$UjNEiZ7DKCr4iTt3.yRD3uAswujYyjRdzi8UsCI4S1UAbVldSPCWu'), + -- username: beep, password: boop + ('beep','$2a$10$n4si2D1TNuaFohFTsxx/A.HAlDRag2cs6fkwmU2XdYiAYLGmmXTOi'); --- puzzles 1-4 all valued at 25 -INSERT INTO puzzles (value) -VALUES ( - (25), - (25), - (25), - (25) -); +-- puzzles 1-3, values at 33,33, and 34 (total 100) +INSERT INTO puzzles (name, value) +VALUES + ('Lock_and_key',34), + ('Light_up_tree',33), + ('Open_the_present',33); --- users 1-3 with puzzles 1-4 all unsolved +-- users 1-3 with puzzles 1-3 all unsolved INSERT INTO users_puzzles (user_id, puzzle_id) -VALUES ( +VALUES (1,1), (1,2), (1,3), - (1,4), (2,1), (2,2), - (2,3), - (2,4), - (3,1), - (3,2), - (3,3), - (3,4) -); + (2,3); + -- (3,1), + -- (3,2), + -- (3,3); --- items 1-4 all unknown -INSERT INTO items (status) -VALUES ( - ('unknown'), - ('unknown'), - ('unknown'), - ('unknown') -); \ No newline at end of file +-- items 1 set to unknown +INSERT INTO items (name,status) +VALUES + ('key','unknown'); \ No newline at end of file diff --git a/ProjectSourceCode/src/resources/css/style.css b/ProjectSourceCode/src/resources/css/style.css index 99883fc..fb872bc 100644 --- a/ProjectSourceCode/src/resources/css/style.css +++ b/ProjectSourceCode/src/resources/css/style.css @@ -11,6 +11,23 @@ text-align: center; } +.footer { + height:10vh; + background-color: #2e5d31; + color: white; + padding: 10px; + width: 100%; + font-family: 'Mountains of Christmas'; + text-align: center; +} + +.footer a { + color: white; + padding: 5px 10px; + transition: background .3s; + font-weight: bolder; +} + .non-header { height:90vh; } @@ -42,7 +59,7 @@ font-weight: bolder; } -.login { +.login, .register { height: 100%; margin: 0; display: flex; diff --git a/ProjectSourceCode/src/resources/plain_svg/scene1.svg b/ProjectSourceCode/src/resources/plain_svg/scene1.svg new file mode 100644 index 0000000..c5aadac --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene1.svg @@ -0,0 +1,834 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene1_test.svg b/ProjectSourceCode/src/resources/plain_svg/scene1_test.svg new file mode 100644 index 0000000..6b28126 --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene1_test.svg @@ -0,0 +1,843 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene2.svg b/ProjectSourceCode/src/resources/plain_svg/scene2.svg deleted file mode 100644 index 71884b1..0000000 --- a/ProjectSourceCode/src/resources/plain_svg/scene2.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - diff --git a/ProjectSourceCode/src/resources/plain_svg/scene2_cutting_cookies.svg b/ProjectSourceCode/src/resources/plain_svg/scene2_cutting_cookies.svg new file mode 100644 index 0000000..77030c0 --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene2_cutting_cookies.svg @@ -0,0 +1,283 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene2_making_dough.svg b/ProjectSourceCode/src/resources/plain_svg/scene2_making_dough.svg new file mode 100644 index 0000000..21575ec --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene2_making_dough.svg @@ -0,0 +1,251 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene3_part1.svg b/ProjectSourceCode/src/resources/plain_svg/scene3_part1.svg new file mode 100644 index 0000000..907e22b --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene3_part1.svg @@ -0,0 +1,331 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene3_part2.svg b/ProjectSourceCode/src/resources/plain_svg/scene3_part2.svg new file mode 100644 index 0000000..215ad2a --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene3_part2.svg @@ -0,0 +1,257 @@ + + + + diff --git a/ProjectSourceCode/src/resources/plain_svg/scene4.svg b/ProjectSourceCode/src/resources/plain_svg/scene4.svg new file mode 100644 index 0000000..5096fe6 --- /dev/null +++ b/ProjectSourceCode/src/resources/plain_svg/scene4.svg @@ -0,0 +1,513 @@ + + + + diff --git a/ProjectSourceCode/src/views/pages/login.hbs b/ProjectSourceCode/src/views/pages/login.hbs index 289a129..03954c2 100644 --- a/ProjectSourceCode/src/views/pages/login.hbs +++ b/ProjectSourceCode/src/views/pages/login.hbs @@ -1,3 +1,5 @@ +{{>message}} +