This assignment builds on the work you have done for Assignment 4.
In this assignment you will focus on building the backend service for the bookstore. Here is a high-level overview of what you will be doing:
- Set up a MongoDB database
- Build a simple web server using Node.js
- Build a check-out routine using AJAX POST
-
You will need Node.js to complete this assignment, so please install Node.js if you haven't done so yet. Node.js comes with a tool called NPM (Node Package Manager), which you can use to download Node.js modules written by other people. After you have installed Node.js, you will need the following NPM modules (but don't install these NPM modules yet, you will do this in Task 1):
express
(npm)mongodb
(npm)
-
You will also need MongoDB to store the data that your application will be accessing.
-
In addition, we provide you with a boilerplate and a some utility functions - you will find them in the instructions below.
We use the following object schemas to describe the structure of objects:
Product = {
"_id": ObjectId(String),
"label": String,
"price" : Number,
"quantity" : Number,
"imageUrl": String,
"category": String
}
ProductArray = [ Product, Product, Product, ... ]
Products = {
Product._id: {
"label": Product.label,
"price": Product.price,
"quantity": Product.quantity,
"imageUrl": Product.imageUrl,
},
Product._id: {
"label": Product.label,
...
},
Product._id: {
"label": Product.label,
...
},
...
}
Cart = {
Product._id: Number,
Product._id: Number,
Product._id: Number,
...
}
Order = {
"_id": ObjectId(String),
"client_id": String,
"cart": Cart,
"total": Number
}
Query = {
minPrice: Number|String,
maxPrice: Number|String,
category: String
}
For example, when we refer to a "Product
object" in the instructions below, we mean an object with fields "label"
, "price"
, "quantity"
,"imageUrl"
,"category"
as shown in the schema above. The field "_id"
exists in the case of a MongoDB document.
-
(2 Points) We will take a few small steps first to prepare for "full-stack" development.
- A) Reorganize your project directory to include the server application. A boilerplate of the application has been provided for you and you're welcome to copy it and start from there (if you copied it before, take a look at it again as it's been updated). Your project directory should now have the following structure:
/public/ /css/ /js/ /index.html /index.js /initdb.mongo /package.json /StoreDB.js
- When you're moving things within your project directory, make sure you don't move the
.git
directory and.gitignore
file. - Make sure in your
.gitignore
file, you have a line**/node_modules/**
(you should already have this as we included it upon setting up the repo). This tellsgit
not to commit your NPM dependencies. - The
/public/
directory should now contain the files you had until now (up toassignment-4
). - You can now remove the
/images
directory as you will be fetching the images from a different server. - Once you've reorganized the directory, install the NPM dependencies mentioned above. You should see a
/node_modules
directory once you've installed them. - If everything is set up properly, you should now be able to serve your client-side application by typing
node index.js
(index.js
is your server application). The server should bind to port 3000 and your client app should be accessible via a browser athttp://localhost:3000
.
- When you're moving things within your project directory, make sure you don't move the
- B) Initialize the database using the script provided. Start a Mongo Shell by typing
mongo
, then load the script byload("initdb.mongo")
. It initializes two collections,products
andorders
, and it will populate your database with the product list. You can typedb.products.find()
to see if the products were entered properly. If you need to reset your database, simply repeat theload
command.
- A) Reorganize your project directory to include the server application. A boilerplate of the application has been provided for you and you're welcome to copy it and start from there (if you copied it before, take a look at it again as it's been updated). Your project directory should now have the following structure:
-
(2 Points) [JS+HTML] To interact with the server, we will make some modifications in the client app. To help you focus on server-side development, we're providing you with some client-side functions that you can copy+paste into your application. You will need to set up the rest of your code to properly integrate the functions we're providing.
- A) In
assignment-4
you initializedstore
with the URLhttps://cpen400a-bookstore.herokuapp.com
. Change this URL and use the URL of your server instead. - B) Declare a new global variable
displayed
and assign an empty array. This array will store the keys of products that should be displayed in the view. - C) Previously in the
renderProductList
function, you iterated over the keys ofstoreInstance.stock
. Modify this loop to iterate over thedisplayed
array. - D) Previously, after you initialized the
store
instance, you calledstore.syncWithServer
with no arguments. Now pass in a callback function with signaturefunction(delta)
. In the callback function, update thedisplayed
array, assigning the keys of thedelta
object. Then invokerenderProductList
with the appropriate arguments. - E) We are providing 2 functions for you to use here. Copy them into your client application. You are also welcome to write your own or modify the given code, but you will be responsible for making them work.
- The first is a
Store
method calledqueryProducts
with the signaturefunction(query, callback)
. This method takes in aquery
object, makes an AJAX GET request with the appropriate query string, then invokesonUpdate
upon getting a response, and finally invokes thecallback
function. - The second is a rendering function named
renderMenu
with the signaturefunction(container, storeInstance)
. It renders the product category menu and the price filters on the given DOM element.
- The first is a
- F) To use the provided functions, replace the
#menu
element with adiv#menuView
in your HTML. This is where you will render the product category menu. In your JavaScript where you definestore.onUpdate
handler, add a call torenderMenu
, passing in the appropriate arguments.
- A) In
-
(3 Points) [JS] We can use the
mongodb
library to interact with the database. However, the API provided by the module are quite low-level and we want to abstract out these details so we don't have to call the low-level methods all the time. In this task we will write aStoreDB
object that will take care of the low-level operations. We have provided a template forStoreDB
, which you will complete.- A) Complete the implementation for
StoreDB.prototype.getProducts
.- It should accept a single argument
queryParams
, which is aQuery
object. - It should find documents in the MongoDB
products
collection, filtered according to thequeryParams
object. - If
"minPrice"
is given, the result should only contain products with price higher than or equal to"minPrice"
. - If
"maxPrice"
is given, the result should only contain products with price lower than or equal to"maxPrice"
. - If
"category"
is given, the result should only contain products with category equal to"category"
. - Any combination of the above query parameters should work.
- It should return a
Promise
that resolves to aProducts
object we've been working with so far (not the raw MongoDB result). Make sure you don't return an array (while the standard practice is returning an array, we'll return an associative array so that we don't have to make a lot of changes on the client side)
- It should accept a single argument
- B)
require
the moduleStoreDB.js
in your server application (i.e.index.js
). Then declare a variabledb
, assigning aStoreDB
instance initialized with the appropriate arguments (look ininitdb.mongo
for the database name).
- A) Complete the implementation for
-
(2 Points) [JS] In your server app, create a HTTP
GET
endpoint/products
that returns the products in the database.- Use the Express.js API to define a GET endpoint.
- The handler for this endpoint should use
db.getProducts
to read aProducts
object. - The handler should pass a
Query
object (created from the query-string) as the argument intodb.getProducts
function. - If the database operation was successful, send the
Products
object as a JSON object. - If the database operation was unsuccessful, set the response status to 500 and send an error message.
-
(1 Point) [JS] Write a reusable function that can be used to make an AJAX POST request, similar to the
ajaxGet
function you implemented inassignment-4
. The function should have the following signature:ajaxPost(url, data, onSuccess, onError)
.- The first argument
url
is a string representing the URL to make request to. - The second argument
data
is the object to attach as the payload of the request. - The third argument
onSuccess
is a function that is called if the request was successful, and it takes in a single argument - the response returned from the server. - The fourth argument
onError
is a function that is called if the request failed or timed out, and it also takes in a single argument - the error of the request. - Unlike
ajaxGet
, you do not need to retry the request in case of failure; simply invoke theonError
callback with the appropriate error. - Upon successfully receiving a response, invoke the
onSuccess
callback function. Convert the response payload into a JavaScript object and then pass it as the argument. - The request payload should be a JSON string, and the request header should be set accordingly to indicate that the payload is JSON (i.e. the
Content-Type
header should be"application/json;charset=UTF-8"
).
- The first argument
You should be able to use the function like this:
ajaxPost("http://localhost:3000/checkout",
{
// some object
},
function(response){
// do something with the response
},
function(error){
// do something with the error
}
);
-
(1 Point) [JS] Update
Store.prototype.checkOut
to make an actual AJAX POST request.- In the place where you alert the user the total amount due, remove the alert and use
ajaxPost
to make an AJAX POST request to the/checkout
endpoint. - You should pass in an
Order
object as thedata
argument for theajaxPost
call.- You can generate a random number to use for the
client_id
field. cart
field should be the currentcart
object.total
field should be the total amount due.
- You can generate a random number to use for the
- If POST request is successful, do the following:
- alert the user that the items were successfully checked out.
- set the
cart
to an empty object. - invoke
onUpdate
with no arguments.
- If POST request is unsuccessful, do the following:
- alert the user about the error
- In the place where you alert the user the total amount due, remove the alert and use
-
(2 Points) [JS] In
StoreDB.js
, complete the implementation forStoreDB.prototype.addOrder
.- It should accept a single argument
order
, which is anOrder
object. - It should insert an
Order
document in the MongoDBorders
collection. - It should also decrement the quantities in the
products
collection accordingly. That is, ifcart
field contains{ Box1: 3 }
, 3 should be decremented from the product with_id
="Box1"
in theproducts
collection. - It should return a
Promise
that resolves to the ObjectId of the newly inserted Mongo Document.
- It should accept a single argument
-
(2 Points) [JS] Create a HTTP
POST
endpoint/checkout
that accepts an order and inserts it in the database.- The handler for this endpoint should accept an
Order
object in the payload of the request, sanitize the object, and then use it as the argument fordb.addOrder
. By sanitize we mean: check that theOrder
object has the correct fields and type-checks. - The handler for this endpoint should use
db.addOrder
to insert the order into the database. - If the database operation was successful, attach the resolved ID to the response as a JSON object.
- If the database operation was unsuccessful, set the response status to 500 and add an error message.
- The handler for this endpoint should accept an
There are 8 tasks for this assignment (Total 15 Points):
- Task 1: 2 Points
- Task 2: 2 Points
- Task 3: 3 Points
- Task 4: 2 Points
- Task 5: 1 Point
- Task 6: 1 Point
- Task 7: 2 Points
- Task 8: 2 Points
- Create a branch called
assignment-5
. - Update the code to reflect the changes for this assignment.
- Make sure you commit and push your changes before the due date - late submissions will not be accepted.
These deadlines will be strictly enforced; we won't be looking at any commits done after this time-stamp.
- L1A & L1B - Monday, November 26, 2018 23:59:59 PST
- If you cannot attend the lab to demo your assignment for any reason, you need to notice Instructors on Piazza ahead of at least 24 hours before the lab section starts. Otherwise, you will be recorded as no attendance and will have marks deducted.