diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..b58b51eb1 Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.gitignore 2 b/.idea/.gitignore 2 new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore 2 @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..77c6ec20c --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +server.js \ No newline at end of file diff --git a/.idea/a3-persistence 2.iml b/.idea/a3-persistence 2.iml new file mode 100644 index 000000000..a447731a9 --- /dev/null +++ b/.idea/a3-persistence 2.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/a3-persistence.iml b/.idea/a3-persistence.iml new file mode 100644 index 000000000..a447731a9 --- /dev/null +++ b/.idea/a3-persistence.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings 2.xml b/.idea/jsLibraryMappings 2.xml new file mode 100644 index 000000000..6af377427 --- /dev/null +++ b/.idea/jsLibraryMappings 2.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 000000000..6af377427 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..5263f304e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs 2.xml b/.idea/vcs 2.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs 2.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 1ba2c7a4c..af639d079 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,59 @@ -Assignment 3 - Persistence: Two-tier Web Application with Database, Express server, and CSS template -=== - -Due: September 22nd, by 11:59 AM. - -This assignnment continues where we left off, extending it to use the most popular Node.js server framework (express), -a database (mongodb), and a CSS application framework / template of your choice (Boostrap, Material Design, Semantic UI, Pure etc.) - -Baseline Requirements ---- - -Your application is required to implement the following functionalities: - -- a `Server`, created using Express (no alternatives will be accepted for this assignment) -- a `Results` functionality which shows all data associated with a logged in user (except passwords) -- a `Form/Entry` functionality which allows users to add, modify, and delete data items (must be all three!) associated with their user name / account. -- Use of at least five [Express middleware packages](https://expressjs.com/en/resources/middleware.html). Explore! One of these five middleware -can be a custom function that you write yourself; if you choose to do this, make sure to describe what this function is in your README. -- Persistent data storage in between server sessions using [mongodb](https://www.mongodb.com/cloud/atlas) -- Use of a [CSS framework or template](https://github.com/troxler/awesome-css-frameworks). -This should do the bulk of your styling/CSS for you and be appropriate to your application. -For example, don't use [NES.css](https://nostalgic-css.github.io/NES.css/) (which is awesome!) unless you're creating a game or some type of retro 80s site. - -Your application is required to demonstrate the use of the following concepts: - -HTML: -- HTML input tags and form fields of various flavors (`"; + + let saveButton = document.createElement('button'); + saveButton.textContent = 'Save'; + saveButton.classList.add('btn', 'btn-success'); + saveButton.style.height = '50px'; + saveButton.style.width = '60px'; + saveButton.onclick = save; + cells[4].innerHTML = ''; + cells[4].append(saveButton); + } +} + +const save = function(e) { + // stop our form submission from refreshing the page + e.preventDefault(); + + e = e || window.event; + var target = e.target; + while (target && target.nodeName !== "TR") { + target = target.parentNode; + } + if (target) { + let cells = target.getElementsByTagName("td"); + + if (modifySubject.value == '' || modifyReceiver.value == '' || modifyMessage.value == '') { + alert("Fill in all the fields") + } else { + + let updatedMessage = { "subject": modifySubject.value, "receiver": modifyReceiver.value, "message": modifyMessage.value } + + cells[1].innerHTML = modifySubject.value; + cells[2].innerHTML = modifyReceiver.value; + cells[3].innerHTML = modifyMessage.value; + + let editButton = document.createElement('button'); + editButton.textContent = 'Edit'; + editButton.classList.add('btn', 'btn-primary'); + editButton.style.height = '50px'; + editButton.style.width = '60px'; + editButton.onclick = edit + cells[4].innerHTML = ''; + cells[4].append(editButton); + + let body = JSON.stringify({ "user": user, "message": updatedMessage}) + console.log("body:", body) + + fetch('/update', { + method: 'POST', + body, + headers: { + //bodyparser only kicks in if the content type is application/json + "Content-Type": "application/json" + } + }) + .then(function(response) { + response.text().then(function(textdata) { + console.log(JSON.parse(textdata)) + }) + }) + } + + } + return false +} + +function displayTable(data) { + const tableBody = document.getElementById("messages") + + const cellID = document.createElement("td") + cellID.appendChild(document.createTextNode(String(data._id))) + tableIDs.push(String(data._id)) + cellID.style.display = "none" + + const cellSubject = document.createElement("td") + cellSubject.colSpan = "1" + cellSubject.appendChild(document.createTextNode(String(data.message.subject))) + + const cellReceiver = document.createElement("td") + cellReceiver.colSpan = "1" + cellReceiver.appendChild(document.createTextNode(String(data.message.receiver))) + + const cellMessage = document.createElement("td") + cellMessage.colSpan = "2" + cellMessage.appendChild(document.createTextNode(String(data.message.message))) + + const cellEdit = document.createElement("td") + cellEdit.colSpan = "1" + var editButton = document.createElement('button') + editButton.textContent = 'Edit'; + editButton.classList.add('btn', 'btn-primary'); + editButton.style.height = '50px'; + editButton.style.width = '60px'; + editButton.onclick = edit + cellEdit.appendChild(editButton) + + const cellDelete = document.createElement("td") + cellDelete.colSpan = "1" + var deleteButton = document.createElement('button') + deleteButton.textContent = 'Delete'; + deleteButton.classList.add('btn', 'btn-danger'); + deleteButton.style.height = '50px'; + deleteButton.onclick = remove + cellDelete.appendChild(deleteButton) + + const newRow = document.createElement("tr") + newRow.append(cellID, cellSubject, cellReceiver, cellMessage, cellEdit, cellDelete) + + tableBody.appendChild(newRow) +} \ No newline at end of file diff --git a/public/js/scripts.js b/public/js/scripts.js new file mode 100644 index 000000000..722b2635e --- /dev/null +++ b/public/js/scripts.js @@ -0,0 +1,233 @@ +let messagesForm = document.getElementById("form"); +let submitButton = document.getElementById("submit"); +let table = document.getElementById("messages"); + +let tableIDs = [] + +let user = localStorage["username"]; + +document.getElementById("welcomeMessage").innerHTML = `Hello, ${user}!` + +// fetch the initial list of messages +fetch("/send", { + method: "POST", //tells it to use the post method + body: JSON.stringify({ "user": user }), + headers: { + //bodyparser only kicks in if the content type is application/json + "Content-Type": "application/json" + } +}) + .then(response => response.json()) // parse the JSON from the server + .then(messages => { + console.log("messages: ", messages) + // iterate through every message and add it to our page + for (let message of messages) { + console.log("messages: ", message) + displayTable(message) + } + }); + +submitButton.addEventListener("click", event => { + + // stop our form submission from refreshing the page + event.preventDefault(); + + const inputSubject = String(document.querySelector('#subject').value) + const inputReceiver = String(document.querySelector('#receiver').value) + const inputMsg = String(document.querySelector('#message').value) + + if (inputSubject === '' || inputReceiver === '' || inputMsg === '') { + alert("Fill in all the fields") + } else { + + let newMessage = { "subject": inputSubject, "receiver": inputReceiver, "message": inputMsg } + + fetch("/add", { + method: "POST", //tells it to use the post method + body: JSON.stringify({ "message": newMessage, "user": user }), + headers: { + //bodyparser only kicks in if the content type is application/json + "Content-Type": "application/json" + } + }) + .then(response => response.json()) + .then(json => { + console.log("message: ", json) + displayTable(json) + }) + + // reset form + messagesForm.reset(); + } +}); + +const remove = function(e) { + // stop our form submission from refreshing the page + e.preventDefault(); + + e = e || window.event; + var target = e.target; + while (target && target.nodeName !== "TR") { + target = target.parentNode; + } + if (target) { + let cells = target.getElementsByTagName("td"); + let body = JSON.stringify({ "_id": cells[0].innerHTML }) + console.log("body:", body) + + let index = tableIDs.indexOf(String(cells[0].innerHTML)) + 1 + + if (index != null) { + table.deleteRow(index) + + fetch('/remove', { + method: 'POST', + body, + headers: { + //bodyparser only kicks in if the content type is application/json + "Content-Type": "application/json" + } + }) + .then(function(response) { + response.text().then(function(textdata) { + console.log(JSON.parse(textdata)) + }) + }) + } + } + return false +} + +const edit = function(e) { + // stop our form submission from refreshing the page + e.preventDefault(); + + e = e || window.event; + var target = e.target; + while (target && target.nodeName !== "TR") { + target = target.parentNode; + } + if (target) { + + let cells = target.getElementsByTagName("td"); + + let index = tableIDs.indexOf(String(cells[0].innerHTML)) + 1; + + var subject = cells[1].innerHTML; + console.log("Subject: ", String(subject)) + cells[1].innerHTML = ""; + + let receiverValue = cells[2].innerHTML; + cells[2].innerHTML = ""; + + let msgValue = cells[3].innerHTML; + cells[3].innerHTML = ""; + + let saveButton = document.createElement('button'); + saveButton.textContent = 'Save'; + saveButton.classList.add('btn', 'btn-success'); + saveButton.style.height = '50px'; + saveButton.style.width = '60px'; + saveButton.onclick = save; + cells[4].innerHTML = ''; + cells[4].append(saveButton); + } +} + +const save = function(e) { + // stop our form submission from refreshing the page + e.preventDefault(); + + e = e || window.event; + var target = e.target; + while (target && target.nodeName !== "TR") { + target = target.parentNode; + } + if (target) { + let cells = target.getElementsByTagName("td"); + + if (modifySubject.value == '' || modifyReceiver.value == '' || modifyMessage.value == '') { + alert("Fill in all the fields") + } else { + + let updatedMessage = { "subject": modifySubject.value, "receiver": modifyReceiver.value, "message": modifyMessage.value } + + cells[1].innerHTML = modifySubject.value; + cells[2].innerHTML = modifyReceiver.value; + cells[3].innerHTML = modifyMessage.value; + + let editButton = document.createElement('button'); + editButton.textContent = 'Edit'; + editButton.classList.add('btn', 'btn-primary'); + editButton.style.height = '50px'; + editButton.style.width = '60px'; + editButton.onclick = edit + cells[4].innerHTML = ''; + cells[4].append(editButton); + + let body = JSON.stringify({ "user": user, "message": updatedMessage}) + console.log("body:", body) + + fetch('/update', { + method: 'POST', + body, + headers: { + //bodyparser only kicks in if the content type is application/json + "Content-Type": "application/json" + } + }) + .then(function(response) { + response.text().then(function(textdata) { + console.log(JSON.parse(textdata)) + }) + }) + } + + } + return false +} + +function displayTable(data) { + const tableBody = document.getElementById("messages") + + const cellID = document.createElement("td") + cellID.appendChild(document.createTextNode(String(data._id))) + tableIDs.push(String(data._id)) + cellID.style.display = "none" + + const cellSubject = document.createElement("td") + cellSubject.colSpan = "1" + cellSubject.appendChild(document.createTextNode(String(data.message.subject))) + + const cellReceiver = document.createElement("td") + cellReceiver.colSpan = "1" + cellReceiver.appendChild(document.createTextNode(String(data.message.receiver))) + + const cellMessage = document.createElement("td") + cellMessage.colSpan = "2" + cellMessage.appendChild(document.createTextNode(String(data.message.message))) + + const cellEdit = document.createElement("td") + cellEdit.colSpan = "1" + var editButton = document.createElement('button') + editButton.textContent = 'Edit'; + editButton.classList.add('btn', 'btn-primary'); + editButton.style.height = '50px'; + editButton.style.width = '60px'; + editButton.onclick = edit + cellEdit.appendChild(editButton) + + const cellDelete = document.createElement("td") + cellDelete.colSpan = "1" + var deleteButton = document.createElement('button') + deleteButton.textContent = 'Delete'; + deleteButton.classList.add('btn', 'btn-danger'); + deleteButton.style.height = '50px'; + deleteButton.onclick = remove + cellDelete.appendChild(deleteButton) + + const newRow = document.createElement("tr") + newRow.append(cellID, cellSubject, cellReceiver, cellMessage, cellEdit, cellDelete) + + tableBody.appendChild(newRow) +} \ No newline at end of file diff --git a/public/login-failed.html b/public/login-failed.html new file mode 100644 index 000000000..4934f0916 --- /dev/null +++ b/public/login-failed.html @@ -0,0 +1,44 @@ + + + + + + Login + + + + + + + + + + + + +
+
+

Messenger

+ +
+ + +
+ +
+ +

OR

+ +
+
+ + + + + \ No newline at end of file diff --git a/public/messenger.html b/public/messenger.html new file mode 100644 index 000000000..f03fca9fd --- /dev/null +++ b/public/messenger.html @@ -0,0 +1,82 @@ + + + + + + Messenger + + + + + + + + + + + + +
+

Messenger

+

+

+

+
+ +
+ +
+ +
+
+
+
+
Don't wait to send a message to whom you love.
+ + + + + + +
+
+
+
 
+ + +
+ +
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + +
SubjectReceiverMessage
+
+ + + + + \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 000000000..68af9e53a --- /dev/null +++ b/server.js @@ -0,0 +1,160 @@ +const express = require("express"), + bodyParser = require("body-parser"), + cookie = require('cookie-session'), + path = require('path'), + app = express(), + mongodb = require("mongodb"), + favicon = require('serve-favicon'), + serveStatic = require('serve-static'), + morgan = require('morgan'); + +var ObjectId = require('mongodb').ObjectId; +let collection = null; + +//Serve static files using middleware +app.use(serveStatic(path.join(__dirname, 'public'))) + +//Serve favicon using middle ware +app.use(favicon(__dirname + '/public/assets/sending.jpg')); + +//create a token +morgan.token('body', function(req, res) { + return [ + JSON.stringify(req.body) + ] +}) + +//create logger using morgan middleware +app.use(morgan(':method :url :status :res[content-length] - :response-time ms :body')) + +const uri = "mongodb+srv://capricieuxV:wangshiyue@cs4241-a3.b9hmqhk.mongodb.net/?retryWrites=true&w=majority"; + +const client = new mongodb.MongoClient(uri, { + useNewUrlParser: true, + useUnifiedTopology: true +}); + +client.connect() + .then(() => { + // will create collection if it doesn't exist + return client.db("data").collection("collection"); + }) + .then(__collection => { + // store reference to collection + collection = __collection + // blank query returns all documents + return collection.find({}).toArray() + }) +.then(console.log) + +app.post("/send", bodyParser.json(), (request, response) => { + if (collection !== null) { + collection + .find({ "user": request.body.user }) + .toArray() + .then(result => response.json(result)) + .catch(err => console.log(err)); + } +}); + +app.post("/addUser", bodyParser.json(), (request, response) => { + if (collection !== null) { + collection + .find({ "username": request.body.username }) + .toArray() + .then(result => { + if (result.length === 0) { + collection.insertOne(request.body) + .then(insertResponse => collection.findOne(insertResponse.insertedId)) + .then(findResponse => { + response.json({ "newUser": "1" }) + }); + } else { + response.json({ "newUser": "0" }) + } + }) + .catch(err => console.log(err)); + } +}); + +// use express.urlencoded to get data sent by default form actions +// or GET requests +app.use(express.urlencoded({ extended: true })) + +// The keys are used for encryption and should be changed +app.use(cookie({ + name: 'cookie', + keys: ['12345667890'] +})) + +app.post('/login', (request, response) => { + // express.urlencoded will put your key value pairs + // into an object, where the key is the name of each + // form field and the value is whatever the user entered + + collection.find({ "username": request.body.username }).toArray(function(err, results) { + if (err) { + console.log(err); + } else { + if (results[0] === undefined) { + request.session.login = false + console.log("login failed") + // username incorrect, redirect back to login page + response.sendFile(__dirname + '/public/login-failed.html') + } else if (results[0].password === request.body.password) { + // define a variable that we can check in other middleware + // the session object is added to our requests by the cookie-session middleware + request.session.login = true + console.log("login succeed") + // since login was successful, send the user to the main content + response.redirect('messenger.html') + } else { + request.session.login = false + // password incorrect, redirect back to login page + response.sendFile(__dirname + '/public/login-failed.html') + } + } + }) +}) + +// add some middleware that always sends unauthenicated users to the login page +app.use(function(request, response, next) { + if (request.session.login === true) + next() + else + response.sendFile(__dirname + '/public/login-failed.html') +}) + +app.post("/add", bodyParser.json(), (request, response) => { + console.log("body:", request.body); + collection.insertOne(request.body) + .then(insertResponse => collection.findOne(insertResponse.insertedId)) + .then(findResponse => { + console.log(findResponse) + response.json(findResponse) + }); +}); + +app.post("/remove", bodyParser.json(), (request, response) => { + collection + .deleteOne({ _id: ObjectId(request.body._id) }) + .then(result => { + console.log(result) + response.json(result) + }); +}); + +app.post('/update', bodyParser.json(), (request, response) => { + console.log("id: ", request.body._id) + collection + .updateOne({ _id: ObjectId(request.body._id) }, { $set: { review: request.body.review, user: request.body.user } }) + .then(result => { + console.log(result) + response.json(result) + }); +}); + + +const listener = app.listen(80, () => { + console.log("Your app is listening on port " + 80); +});