Description
Overview The primary task of Project 3 is to implement the website for our markdown-based blogging service that (1) lets anyone read blogs written by our users through public URLs and (2) lets our registered users create and update their own blogs after password authentication. Through this process, we will learn how to develop a back-end service using NodeJS, Express and MongoDB. Note that your implementation of Project 3 will be used for your Project 4 as well. Since Project 4 is dependent on Project 3, it is important that you follow instructions on this spec exactly to avoid any potential issues later in Project 4. Development Environment All development for Projects 3 (and 4) will be done on a Docker container based on the “junghoo/cs144-mean” image: $ docker run -it -p3000:3000 -p4200:4200 -v {host_shared_dir}:/home/cs144/shared –name mean junghoo/cs144-mean Make sure to replace {host_shared_dir} with the name of the shared directory on your host. The above command creates a docker container named mean with appropriate port forwarding and directory sharing. Once created, you can start the container simply by issuing the following command in a terminal window: $ docker start -i mean This container has MongoDB (v3.6.8), NodeJS (v8.12.0), Express application generator (v4.16.0), and Angular CLI (v7.0.3) pre-installed. Make sure that they run fine through the following commands: 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 2/14 $ mongo –version $ node –version $ express –version $ ng –version Project Requirements Our back-end blogging service should be accessible at the following URLs: # URL method functionality 1 /blog/:username/:postid GET Return an HTML-formatted page that shows the blog post with postid written by username. 2 /blog/:username GET Return an HTML page that contains first 5 blog posts by username. 3 /login GET, POST Authenticate the user through username and password. 4 /api/:username GET This is the REST API used to retrieve all blog posts by username 5 /api/:username/:postid GET, POST, PUT, DELETE This is the REST API used to perform a CRUD operation on the user’s blog post Note: 1. The URL patterns 1-3 should be publicly accessible by anyone. No prior user authentication should be required to access these URLs. More detailed requirements for the URL patterns 2 and 3 will be given in Parts B and C, respectively. 2. The URL patterns 4-5 should be protected behind authentication. More detailed specifications on this API will be given later in Part D. 3. The implemented server should listen on port 3000 for HTTP requests. All blog posts and the users’ authentication credentials should be stored in the MongoDB server. The MongoDB server should have at least the following two collections, “Posts” and “Users”, in the database “BlogServer”. The two collections must have two initial documents shown below: 1. Collection: Posts { “postid”: 1, “username”: “cs144”, “created”: 1518669344517, “modified”: 1518669344517, “title”: “Title 1”, “body”: “Hello, world!” } 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 3/14 { “postid”: 2, “username”: “cs144”, “created”: 1518669658420, “modified”: 1518669658420, “title”: “Title 2”, “body”: “I am here.” } The first collection “Posts” stores all blog posts created and saved by our users. As users write more blog posts, more documents should be inserted into this collection. Note that “created” and “modified” fields of the two documents are all integers, whose values are milliseconds since the the Unix epoch (Jan 1, 1970 UTC). 2. Collection: Users { “username”: “cs144”, “password”: “$2a$10$2DGJ96C77f/WwIwClPwSNuQRqjoSnDFj9GDKjg6X/PePgFdXoE4W6” } { “username”: “user2”, “password”: “$2a$10$kTaFlLbfY1nnHnjb3ZUP3OhfsfzduLwl2k/gKLXvHew9uX.1blwne” } The second collection “Users” stores the users’ authentication credentials. They should be used for authenticating any user to our server through the URL pattern 3. Note that users’ passwords must NEVER be stored in plaintext. Instead, we have to store them only after we apply a cryptographic one-way hash function. This ensures that even if a hacker breaks into our system and gets a hold of our database, they won’t be able to obtain the users’ passwords easily since it is time-consuming to recover the plaintext passwords from the hash values. The downside of this approach is that when a user tries to login, we will have to apply the same cryptographic hash function to the user-provided password and then match the equivalence of this hash value to what is stored in our database. This can potentially increase the computational overhead of authenticating a user, but given the potential security risk of saving plaintext passwords, it is the cost that we are willing to pay. In our case, we applied bcrypt hash function to each user’s password (“password” for “cs144” and “blogserver” for “user2”, respectively) using the bcryptjs module of node.js. Part A: Create Initial MongoDB Data In Project 3, all blog posts must be managed by MongoDB. Unlike MySQL, MongoDB doesn’t have the concept of schema. All types of data are saved as documents in a collection. Since MongoDB document is essentially a JSON object, it is often a preferred back-end data storage engine for JavaScript-based development. When you start the Docker container, it starts MongoDB server in the background. So you can start the “MongoDB command-line shell” simply by: $ mongo 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 4/14 Once you are inside the shell, you can issue most MongoDB commands interactively. Go over class notes on MongoDB to review the basic MongoDB commands. If needed, review online tutorials on MongoDB, such as this one. Now write a script named db.sh that includes the sequence of mongodb shell commands that load the following documents into the two collections, “Posts” and “Users”, in the “BlogServer” database: 1. Collection: Posts { “postid”: 1, “username”: “cs144”, “created”: 1518669344517, “modified”: 1518669344517, “title”: “Title 1”, “body”: “Hello, world!” } { “postid”: 2, “username”: “cs144”, “created”: 1518669658420, “modified”: 1518669658420, “title”: “Title 2”, “body”: “I am here.” } 2. Collection: Users { “username”: “cs144”, “password”: “$2a$10$2DGJ96C77f/WwIwClPwSNuQRqjoSnDFj9GDKjg6X/PePgFdXoE4W6” } { “username”: “user2”, “password”: “$2a$10$kTaFlLbfY1nnHnjb3ZUP3OhfsfzduLwl2k/gKLXvHew9uX.1blwne” } We also provide two JSON files that contain the above documents, posts.json and users.json, in case they are helpful. Note that “created” and “modified” fields of the first two post documents are all integers, whose values represent the milliseconds since the the Unix epoch (Jan 1, 1970 UTC). You must store all date fields in this format. Also recall that the above password values are obtained by applying the bcrypt cryptographic one-way hash function. Note that your provided script db.sh will be executed through the following command $ mongo < db.sh before we grade your submission to initialize the MongoDB database for the server. Note:: Your db.sh script must strictly follow the instructions above. Please make sure that the database names, the collection names, and their contents are exactly the same as this instruction including their case. Since MongoDB is CASE SENSITIVE, your submission will fail our test script even for a minor case mismatch and lead to a very low grade. Notes on CR/LF issue: If your host OS is Windows, you need to pay attention to how each line ends in your script file. Windows uses a pair of CR (carriage return) and LF (line feed) characters to terminate lines, but Unix uses only a LF character. Therefore, problems may arise when you feed a 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 5/14 text file generated from a Windows program to a Unix tool such as mongo. If you encounter any wired error when you run your script, you may want to run the dos2unix command in the container on your script file to fix line end characters. Part B: Implement Public HTML Blog Web Pages As the second task of Project 3, we now implement the public URLs by which any user can view blog posts published on our website. In particular, we will implement a server that returns an HTML page to an HTTP request to the following URL patterns: # URL method functionality 1 /blog/:username/:postid GET Return an HTML-formatted page that shows the blog post with postid written by username. 2 /blog/:username GET Return an HTML page that contains first 5 blog posts by username. If the user has more than 5 posts, the page should contain a “next” button that link to the next 5 posts (according to the postid) by the user. We use node.js JavaScript runtime engine and its express module to implement our back-end server. Node.js is built on Chrome’s V8 JavaScript engine, which uses an event-driven, non-blocking I/O model to make it lightweight and efficient. Express is a module that provides an easy-to-use routing mechanism, HTML template integration, and third-party middleware integration. You may want to go over the class lecture note on node.js to brush up on node.js and express. If you need more detailed instruction on how to use them, online tutorials like this can be helpful. Express web site has more detailed documentation on routing and using a template engine. Generate Server Skeleton Code To generate the skeleton code for our web server, we will use the Express application generator. Initialize the project under a project directory (e.g. blog-server): $ express -e blog-server Here -e option makes the generated code use the “EJS” template engine for generating HTML pages from JSON. You are welcome to use other template engine for your development, but our project spec gives instructions for “EJS”. When the above command is executed, you see the following folder and file structure within the blog-server directory: 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 6/14 blog-server +- bin +- public +- routes +- views +- package.json +- app.js Here the app.js file serves as the entrance of the whole project. If you define other .js files, you need to reach them starting from app.js. The package.json file contains some meta information on the project including its “package dependencies”. When you add new dependencies to this file and run npm install, npm will install all dependent modules into the subdirectory node_modules. The public directory contains static resources that are made available on the developed web site, like HTML, CSS, JavaScript, and image files. The views directory contains HTML template files. The routes directory contains “middleware” that processes the requests. The bin directory contains the executable file. As you develop your code, you are welcome to add or modify any directory or file as needed. Make sure that the generated code works properly on our container by executing the following command: $ npm start The npm start command executes “test” script specified in package.json (which is node ./bin/www in the auto-generated package.json file). Open a web browser on your host machine and make sure that you see a page similar to the following image at https://localhost:3000/: Install mongodb and commonmark Modules 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 7/14 To generate public HTML blog pages using node.js, express, and MongoDB, we need a MongoDB client library for node.js and a markdown-to-HTML rendering library. We will use the official MongoDB driver for node.js and the commonmark.js library for this purpose. Install these two packages using npm $ npm install –save mongodb $ npm install –save commonmark Make sure that you add the –save option, so that the packages are added as dependencies to package.json. Note that commonmark.js’s API is almost identical to the Java version, so you will find it easy to use. In case you are not familiar with the MongoDB native client API, go over MongoDB native drive quick start tutorial and read the MongoDB tutorial on CRUD operations to learn the basics. Note: Due to a strange interaction between node, Docker container, and shared folder, you may sometimes get an error similar to the following when you run node or npm: path.js:1177 cwd = process.cwd(); ^ Error: ENOENT: no such file or directory, uv_cwd If you get the above error, you can “fix it” by cd ../, cd back, and try again. Learn the Template Syntax The responses to the two URL patterns shown above should be all in HTML. For generating an HTML response, you can use a template engine. When you use a template engine, you just need to write a static “template file”, and “render” the final HTML response by combining the template with data. EJS (Embedded JavaScript) uses a template syntax very similar to Java ServePages (JSP). Like JSP, you can use the standard HTML tags in the template, and sprinkle your JavaScript code inside <% … %>, <%= … %>, or <%- … %> tags. <%= … %> or <%- … %> can include any expression and is replaced with the output string of the expression. The difference between the two is that <%= … %> escapes HTML tags in the output, so that the HTML tags are displayed as strings, while <%- … %> does not escape HTML tags, so that the browser can interpret them as HTML tags. <% … %> can include any arbitrary JavaScript code, not just expressions. Here is an example of a valid EJS template: 12/13/2018 index https://oak.cs.ucla.edu/classes/cs144/project3/index.html 8/14 For more EJS examples, look at the generated template files in the views directory. You may also want to go over an online tutorial like this to learn more on EJS. Implement /blog/:username/:postid and /blog/:username Now that you know the basics to implement Part B, go ahead and implement it. The responses from these two URL patterns must meet the following requirements: 1. All blog posts returned from the two URL patterns should be rendered in HTML from markdown using the commonmark.js module, both title and body. 2. The second URL pattern /blog/:username must return the first 5 posts by username. When there are more posts from the user the returned page must contain a “next” link, which points to a page with the next 5 posts according to the postid by the user. Make sure that the “next” link is implemented as an HTML element with id=”next” and href pointing to the URL of the “next page”. 3. The second URL pattern /blog/:username must take an optional query string parameter start=:postid, like /blog/cs144?start=3 When this optional query string parameter exists, the response must include the next 5 posts by cs144 whose postid is 3 or above. Note: 1. In implementing this part, remember that a request can be “routed” to a callback function through app.METHOD(URL, callback), like app.get(‘/blog/:username’, callback). Inside the callback function, you can reference the HTTP request through the first req parameter, and you can generate the response through the second res parameter. 2. In our project, all dates are stored as a number in MongoDB, which represents milliseconds since the the Unix epoch (Jan 1, 1970 UTC). You can convert a JavaScript Date object to this number using its getTime() method. Conversely, you can convert this number to a Date object by passing it as the constructor parameter of Date or by calling a date object’s setTime() method.
-
- <% for(let i = 0; i < books.length; i++) { %>
- <%- books[i].isbn %>
<% } %>

