List of Figures

Chapter 1. Getting started

Figure 1.1. The contents of a response from an initial request by your browser to https://brushfire.io

Figure 1.2. The frontend makes a request and the backend makes a response.

Figure 1.3. Clicking the Sign Up button generates a request to the Sails backend, which responds with the signup page.

Figure 1.4. The components necessary for the backend to satisfy the request and deliver the signup page to the frontend

Figure 1.5. The components necessary for the backend to satisfy the request by creating a user and responding with the result

Figure 1.6. Understanding how the Sails router matches the incoming request with routes to trigger an action

Figure 1.7. The router matched the request as part of an explicit route.

Figure 1.8. A route operates like a click event handler . The route address serves a similar purpose to the combination of the DOM selector and DOM event. And the route target is similar to an event handler function .

Figure 1.9. An example of the URL query string that starts with a question mark, contains a key/value pair separated by an equals sign, and is separated by an ampersand

Figure 1.10. The components of a model include attributes, methods, and settings.

Figure 1.11. An endpoint from a backend API in action, processing a request from a signup form

Figure 1.12. The frontend-first approach to backend development

Figure 1.13. An interactive mockup of a signup form. The fields in this form help determine the request parameters you should expect when designing the API endpoint to process it.

Figure 1.14. An annotated mockup showing the requests this user interface will send to your Sails app

Figure 1.15. As long as the expected request and response for an API endpoint remain consistent, frontend code doesn’t need to change.

Figure 1.16. Examples of frontend user-agents used in web applications

Figure 1.17. Sails used as a pure API with no responsibilities for delivering frontend assets

Figure 1.18. Typical frontend requests to the backend using the SPA approach

Figure 1.19. Delivering personalized HTML and static assets to a hybrid web application

Figure 1.20. The two security realms of a web application

Chapter 2. First steps

Figure 2.1. Comparing the code of two different projects in Sublime Text with ShiftIt

Figure 2.2. The terminal consists of one or more windows or tabs. Each window or tab contains a command line, where you type commands.

Figure 2.3. The Node installation page for Windows, OS X, and Linux operating systems

Figure 2.4. npm consists of a registry with hundreds of thousands of packages , a command-line-interface application , and individual packages .

Figure 2.5. The globally installed version of Sails generated a new Sails application and installed itself as a dependency of the new project .

Figure 2.6. The Sails server up and running

Figure 2.7. The JSON response from the purpose action.

Figure 2.8. An overview of the GET request to /life/purpose

Figure 2.9. The terminal window responds with the command prompt after you close the Sails server with Ctrl-C.

Figure 2.10. Only one node application at a time can be running on a particular port.

Figure 2.11. A remote GitHub repository with links to all files in the repo as well as a link to clone the repo

Figure 2.12. The cursors Sails project in action

Chapter 3. Using static assets

Figure 3.1. Delivering assets and endpoints to a single-page application

Figure 3.2. After starting the Sails server and navigating to the root route of the project, Sails responds with the default homepage.

Figure 3.3. In addition to an action, an explicit route can have a view as its target . When a request matches the root address of this route, the Sails server responds with a view . In this case, the view is the homepage .

Figure 3.4. The homepage before and after running the static generator

Figure 3.5. Using a server-rendered view, the path is added before the response is sent to the requesting user-agent. Using a static asset, the index.html page isn’t changed between the request and response.

Figure 3.6. The static-asset pipeline aggregates preprocessing tasks to assets.

Figure 3.7. The brushfire/assets/ folder synced with the brushfire/.tmp/ public/ folder

Figure 3.8. The videos.html file synced in both the assets/ and .tmp/public/ folders

Figure 3.9. Interactive mockups are an essential part of the frontend-first approach to development. Each chapter has an online hub that contains mockups. The homepage mockup from the chapter 3 hub is pictured here.

Figure 3.10. The homepage with Bootstrap styling added as a CDN dependency

Figure 3.11. The videos page mockup from the Sails.js in Action hub

Figure 3.12. A GET request to /videos triggers a matching asset route, which displays index.html from brushfire/assets/videos/index.html.

Figure 3.13. The homepage with an image in the navbar using a relative path

Figure 3.14. The source path within the image tag used a relative path that didn’t exist and resulted in a broken link.

Figure 3.15. The page source reveals the reference links to the Bootstrap and importer.css files.

Figure 3.16. By default, an importer.less file is generated for each new Sails project. You’ll see later how importer.less is compiled into importer.css. What’s important here is that the compiled importer.css file was injected into brushfire/assets/index.html because it contains the special Sails linker tags. After adding bootstrap.min.css to the brushfire/assets/styles folder, a link was also automatically added to brushfire/assets/index.html for the bootstrap.min.css file.

Figure 3.17. The page source reveals the reference script tag to the sails.io.js file.

Figure 3.18. By default, a sails.io.js file is generated by each project and placed in the brushfire/assets/js folder. sails-linker inserts a reference to it in brushfire/assets/index.html .

Figure 3.19. The homepage with the new styles in custom.less

Figure 3.20. The videos page has two requests, one for new video submissions and the other for the initial videos of the page.

Figure 3.21. The added video from the video-submit-form using jQuery

Chapter 4. Using the blueprint API

Figure 4.1. The backend API endpoint is triggered by a request from the frontend . The Sails router attempts to match the route , and if it finds a match, executes an action . The action does stuff and responds to the frontend .

Figure 4.2. The video page can send two requests: one to post new video submissions and the other to get the initial videos of the page.

Figure 4.3. Choosing between safe, alter, or drop auto-migration in Sails

Figure 4.4. The shortcut blueprint route triggered the create blueprint action, which created a new video record in the video model and responded with the record’s contents as JSON.

Figure 4.5. The Sails router listens for an incoming request B and tries to match it to an explicit route. Not finding a match with an explicit route, it next tries to match it to a shortcut blueprint route c. In this example it finds a match, executes the create blueprint action d, and e responds with a 200 status code and the newly created video record as JSON.

Figure 4.6. The start of the query string uses a question mark (?) followed by field/value pairs where each pair is separated by an equals sign (=) and the field/value pairs are separated by an ampersand (&).

Figure 4.7. find blueprint actionid parameterThe shortcut blueprint route uses the find blueprint action and responds with a list of records from the video model.

Figure 4.8. When you add an :id parameter to the URL, in this case /3, the response is a single record from the video model.

Figure 4.9. The shortcut blueprint route uses the update blueprint action and responds with the record containing the updated title.

Figure 4.10. The shortcut blueprint route uses the destroy blueprint action and responds with the deleted video record.

Figure 4.11. The steps in a RESTful blueprint route include recognition of the incoming request by the Sails router , matching the request to a route , and executing the find blueprint action , which queries all video records and responds with an array of video record dictionaries.

Figure 4.12. The page triggers the RESTful blueprint route and the find blueprint action and then responds with all records in the video model.

Figure 4.13. POST methodThe RESTful route and a create action were triggered by the Angular AJAX POST request, and the action responded with the newly created record of the video.

Figure 4.14. The RESTful blueprint route and a create action created a new video record based on a POST request to /video.

Figure 4.15. Here, you use Postman to make a GET request to the /video/2 path . The API responds with a JSON dictionary of the particular video record .

Figure 4.16. Here, you use Postman to make a PUT request to the path /video/2 with the URL parameter title=Ludacris . The API responds with a JSON dictionary of the updated video record .

Figure 4.17. Here, you use Postman to make a a DELETE request to /video/2 that contains the record id that you want to delete. The API responds with the JSON video record that was deleted .

Figure 4.18. Replacing the AJAX GET request with the Sails WebSockets GET enhances the functionality of the find action.

Figure 4.19. When the io.socket.post() method made a POST request to /video, the create action added the new video record that emitted a video event. This event was received by the previously added event handler, which triggered an update to the DOM with the new video record.

Chapter 5. Custom backend code

Figure 5.1. After you started the Sails server, the bootstrap.js file was executed, in which your code logged Hello World! to the terminal. Then, you passed control back to Sails by calling cb(). At that point, Sails went about its business, completing the process of lifting the server.

Figure 5.2. The .count() method calculates the number of records in the video model, and then, depending on the result, your code logs the appropriate message. Either way, you pass control back to Sails and it finishes lifting your app.

Figure 5.3. The page for machinepack-youtube provides a list of methods, called machines. In this case, all the machines are related to the YouTube API.

Figure 5.4. The .searchVideos() machine page includes example code that you can copy and paste into your own project, the inputs expected by the machine, including their data types and whether they’re required or optional, and what you can expect when the machine exits.

Figure 5.5. The main page for machinepack-youtube on the npmjs registry

Figure 5.6. The terminal window might display the number of existing records in the video model. But if no records exist, you’ll see this error because of a bug in the example code.

Figure 5.7. The .searchVideos() machine takes three inputs: the query to use when searching YouTube , a Google developer API key , and an optional limit on how many videos to return .

Figure 5.8. Your shiny new API key is displayed after completing the key-creation process in the Google Developers Console.

Figure 5.9. Node-Machine.org provides details about the exits of each machine in every open source machinepack on npm.

Figure 5.10. The data returned by the YouTube API is not in the format that the frontend requires .

Figure 5.11. The transformed data from YouTube, now safely stored as new video records and displayed by the frontend

Chapter 6. Using models

Figure 6.1. The four components of an identity, authentication, personalization, and access control system

Figure 6.2. Model definitions consist of attributes, methods, and settings. In addition, every model is connected with a particular adapter.

Figure 6.3. Clone the repo using the URL from the main repo page for the start of chapter 6.

Figure 6.4. Current organization of the interactive mockups. The profile, edit-profile, and restore-profile pages haven’t yet been connected via links.

Figure 6.5. The signup page contains four user model attributes: email, username, password, and confirmation.

Figure 6.6. The profile page contains an additional user model attribute we’ve named gravatarURL. This attribute will store the URL linking the Gravatar image.

Figure 6.7. The User Administration page adds two user model attributes: admin and banned.

Figure 6.8. The restore-profile page allows a user to remove the deleted state of their profile. This will necessitate adding a deleted attribute to the user model.

Figure 6.9. Clicking this button sends a POST request to /user.

Figure 6.10. The Sails router listens for an incoming request, matches it to a RESTful blueprint route, executes the create blueprint action, and responds with a 200 status code and the newly created user record as JSON.

Figure 6.11. This is the result of the blueprint POST request to /user and the redirect to localhost:1337/user/1. In addition to the input fields that were added to the record, createdAt, updatedAt, and id were also added.

Figure 6.12. Sails looks for the database connection it will use to store and manipulate records for a particular model in brushfire/api/models/User.js . If it doesn’t find a connection, it then looks to model settings . If no connection exists, Sails looks to the internal core default connection, localDiskDb . The default connection uses the sails-disk adapter to access the sails-disk database.

Figure 6.13. The sails-disk database located in brushfire/.tmp/local-DiskDb.db reveals the first user record and the first video record .

Figure 6.14. When you attempt to create a user, a record with an identical email address exists in the database and therefore produces a validation error.

Figure 6.15. Using the blueprint actions, all parameters are returned to the frontend.

Figure 6.16. The generic syntax of a model method includes the model name , model method , criteria , values , the query method , the callback method with error and result arguments , and the callback body .

Figure 6.17. The create model method uses a syntax similar to the find, update, and destroy methods, but without criteria.

Chapter 7. Custom actions

Figure 7.1. The four components of an identity, authentication, personalization, and access control system

Figure 7.2. An explicit route contains a route address and a target.

Figure 7.3. You made a GET request to /user/hello , but there’s something wrong with the action because the loading message displays and then times out.

Figure 7.4. The message from the custom action is logged to the console.

Figure 7.5. Again, you make a GET request to /user/hello . Now that you’ve added a response in the action, Postman receives the response and displays it .

Figure 7.6. You can use these mockups to identify the requests on each page as well as the requirements and expectations of each endpoint.

Figure 7.7. The signup page mockup contains three endpoints. The first request initiates the display of the signup page, which is handled by an asset route. The second request is a link to the restore-profile page that’s handled by an asset route, and the third is a POST request to /user/signup that will be handled by a custom controller action.

Figure 7.8. The incoming POST request to /user matched a blueprint RESTful route that triggered a blueprint create action that ultimately created the user record .

Figure 7.9. Using Postman, create a new user with the custom signup action. This will be a POST request to localhost:1337/user/signup with an email parameter set to [email protected] . The signup action responds with the email address as JSON and a 200 status code .

Figure 7.10. The Emailaddresses.validate() machine page provides example usage.

Figure 7.11. Using Postman, check the validity of the email attribute in the custom signup action. Generate a POST request to /user/signup with an invalid email parameter , with the username and password parameters also provided. The signup action executes the invalid exit and responds with a 400 code and message .

Figure 7.12. You’ll use the Passwords.encryptPassword() machine to encrypt a user’s password before it’s stored in the user record.

Figure 7.13. Using Postman, make a POST request to /user/signup and pass the password parameter to the action , which will respond with the encrypted password .

Figure 7.14. Using Postman, make a POST request to /user/signup and pass the email parameter to the action , which will respond with the gravatarURL .

Figure 7.15. This is an example of the .create() model method of the user model . The attributes of the record to be created are passed as an argument to the method as a dictionary {} or an array of dictionaries [{}]. The .exec() method is also referred to as a deferred function. Using .exec() will give you the flexibility to chain modifier methods together and to make the usage easier to read. For now, you'll use .exec() with an anonymous function as the callback. Within this callback, you’ll pass any errors as the first argument and the resulting data from the method . You can then use the body of the callback to respond, or continue with another task.

Figure 7.16. The POST request to localhost:1337/user/signup passes in the email, username, and password parameters , and the action responds with the created record .

Figure 7.17. The frontend displays an error dictionary returned by the User.create() model method that indicates that one of the unique validations has been violated.

Figure 7.18. The signup page now reflects the appropriate error messages when a duplicate email or username attribute is attempted.

Figure 7.19. The profile page mockup contains four endpoints. The first request initiates the display of the profile page and is handled by an asset route. The second request is a GET request to /user/profile/:id for the initial profile information on the page. The third request is triggered by the Edit button to GET the edit-profile page and is handled by another asset route. The final request is a DELETE request to /user/:id and is handled by a custom controller.

Figure 7.20. The findOne model method consists of the model name , the model method , criteria , the query method , the callback method with error , the callback body , and result arguments .

Figure 7.21. The restore-profile page mockup contains two endpoints. The first request initiates the display of the restore-profile page and is handled by an asset route. The second request is a PUT request to /user/ restoreProfile and is handled by a custom action.

Figure 7.22. The edit-profile page mockup contains five endpoints. The first request initiates the display of the edit-profile page and is handled by an asset route. The second request is a GET request to /user/profile/:id for the initial profile information on the page. The third request is triggered by the Restore Gravatar URL With Current Email Address button and is a PUT request to /user/restoreGravatarURL, which is handled by a custom action. The fourth request is a PUT request to /user/updateProfile and is also handled by a custom action. The final request is a PUT request to /user/changePassword and is handled by a custom action.

Figure 7.23. The administration page mockup contains five endpoints. The first request initiates the display of the administration page and is handled by an asset route. The second request is a GET request to /user/ adminUsers for the profile information of all users. The third request is a toggle to the admin attribute that triggers a PUT request to /user/updateAdmin, which is handled by a custom action. The fourth request is a PUT request to /user/updateBanned and is also handled by a custom action. The final request is a PUT request to /user/updateDeleted and is handled by a custom action.

Chapter 8. Server-rendered views

Figure 8.1. You want to show different assets based on the user authenticated state. Here, if the user is not logged in, you’ll display the log-in form . If the user is logged in, you’ll display their Gravatar image, their email address, and a sign-out button .

Figure 8.2. The four components of an identity, authentication, personalization and access control system

Figure 8.3. Delivering assets and endpoints to a single-page application

Figure 8.4. In a single-page app, the entry page is the anchor page where templates are inserted based on frontend routes and the Angular router.

Figure 8.5. Delivering assets and endpoints to a hybrid web application

Figure 8.6. Using custom routes and backend Sails routing to manage page navigation in Brushfire

Figure 8.7. An incoming GET request to / matches an explicit route that triggers a view named homepage.ejs to be rendered into homepage.html and sent as a response to the requesting user-agent.

Figure 8.8. An incoming GET request to / B matches an explicit route and passes a locals dictionary c to a view named homepage.ejs d. The view contains a special tag that, when rendered, inserts a value from the locals dictionary as a response e to the requesting user-agent.

Figure 8.9. How the Sails router matches the incoming request with routes to trigger an action

Figure 8.10. An explicit route is a JavaScript dictionary made up of a route address and a target.

Figure 8.11. The layout file contains the navigation partial and surrounds the view that’s being rendered.

Figure 8.12. The new navigation section contains both the authenticated and unauthenticated markup states.

Figure 8.13. The homepage view with the sign-in form displayed in its unauthenticated state

Figure 8.14. Using locals through a custom route to bootstrap data in a server-rendered view

Figure 8.15. The homepage view with the simulated authenticated state displaying the authenticated markup

Figure 8.16. The relationship between the layout view and the other views of Brushfire

Figure 8.17. This videos page simulates an unauthenticated user and therefore doesn’t have the markup to add a video.

Chapter 9. Authentication and sessions

Figure 9.1. The four components of an identity, authentication, personalization, and access control system

Figure 9.2. The layout navigation partial has a sign-in form that executes a PUT request to /login when the sign-in button is clicked.

Figure 9.3. A successful challenge to the claim of identity of [email protected]

Figure 9.4. The session consists of a session cookie and the user-agent, the session database, and a process for using a value stored on the session cookie to securely identify each request with a particular user-agent.

Figure 9.5. You started off using client-side routing before transitioning to custom backend routes serving server-rendered views. Now that you have the user’s authenticated state, you can move to a combination of custom routes that trigger a custom action for each view.

Figure 9.6. When the user makes a GET request to /, it will be matched with a custom route that triggers an action that determines the user’s authenticated state and sends the appropriate locals to the layout view, which are then added to the frontend via an Angular controller and displayed based on a directive in the view.

Figure 9.7. Examples of how pathToView is resolved based on the configuration used

Figure 9.8. The browser was redirected to the videos page, but the navigation partial isn’t displaying the markup for the authenticated state.

Figure 9.9. The Brushfire mockup pages with custom routes

Chapter 10. Policies and access control

Figure 10.1. Brushfire controller action list with corresponding custom routes

Figure 10.2. The videos page uses the blueprint find action to fulfill a request for all videos in the video model when the page loads.

Figure 10.3. When the user-agent is authenticated, the videos page adds the submit-video form B, which, when clicked, triggers the blueprint create action.

Figure 10.4. Making a POST request to /video with an unauthenticated user-agent returns a 403 forbidden status code and message .

Figure 10.5. The homepage when accessed via an authenticated user-agent has two actions that need protection: the showSignupPage action and the login action .

Figure 10.6. The homepage when accessed via an unauthenticated user-agent with endpoints that trigger three different controller actions

Figure 10.7. The profile page, when accessed via an authenticated user-agent, adds two unprotected actions.

Figure 10.8. The edit-profile page, when accessed via an authenticated user-agent, adds three unprotected actions.

Figure 10.9. The signup page when accessed via an unauthenticated user adds two unprotected actions: showRestorePage and signup .

Figure 10.10. The restore-profile page, when accessed via an unauthenticated user-agent, adds one unprotected action.

Figure 10.11. The administration page, when accessed via an authenticated user-agent whose admin property is set to true, adds four unprotected actions.

Chapter 11. Refactoring

Figure 11.1. The navigation markup has five basic states based on the conditions of the user-agent: unauthenticated , authenticated without the add tutorial button displayed , authenticated with the add-tutorial button displayed , authenticated and an admin , and authenticated and an admin with the add tutorial button displayed .

Figure 11.2. Sign In and Sign Up now have standalone views.

Figure 11.3. The navigation bar adds a drop-down when the user-agent is authenticated to allow access to the user profile and the ability to log out.

Figure 11.4. The API Reference documents each request’s friendly name , description , incoming parameters (if any) , view (if any) , locals , method and URL path , controller and action , response type , and frontend controller (if any) .

Figure 11.5. The wireframes contain documentation for the attributes and requests of each view.

Figure 11.6. The login request has three different response codes if the user-agent is deleted , banned , or successful .

Figure 11.7. The post-pivot home/search page

Figure 11.8. The browse-tutorials page

Figure 11.9. The User Administration page

Figure 11.10. The profile page with tutorials

Figure 11.11. The profile page with users followed

Figure 11.12. The profile page with users following

Figure 11.13. The first example illustrates the impact of the route order on other single-segment routes when using the :username variable. A GET request to /logout would be matched with the GET /:username route, which isn’t the intended result. In the second example , we placed the GET /:username route below the route to the GET /logout route. The GET request to /logout was matched with the route that contained the GET /logout path, which is what was intended. Note that the two-segment route to GET /profile/edit was unaffected.

Figure 11.14. The notFound.js response

Figure 11.15. The model and attribute for each view are shown if they’re not duplicated in the user-agent authenticated and tutorial-owner views . Therefore, all the attributes in the unauthenticated view are referenced in . Notice that some of the attributes use the term calc for “calculated value.” The value isn't stored as displayed but instead calculated in an action.

Figure 11.16. All the requests for each view are shown based on the state of the user-agent.

Figure 11.17. This tutorial was created by the user sailsinaction, but it’s being viewed by the user nikolatesla. Therefore, nikolatesla can rate and view the tutorial but not edit it.

Figure 11.18. A user-agent who is both authenticated and the owner of the tutorial adds an additional request to modify the tutorial and video. Note that an owner of a tutorial can’t rate their own tutorial.

Figure 11.19. A password-recovery system has several components including a form that captures the email address used when creating the account, an action that generates a one-time token and the email and responds with an email-sent form , and the password-reset form with the reset password action .

Chapter 12. Embedded data and associations

Figure 12.1. A model consists of attributes , methods , settings , and an adapter .

Figure 12.2. The tutorials-detail page contains information from the user, tutorial, video, and rating models.

Figure 12.3. Models have relationships that go in different directions, as depicted by the arrows in the figure.

Figure 12.4. The create-tutorial page contains two parameters and two requests.

Figure 12.5. The create-tutorial request reference contains a description , the method and path of the request , backend notes , incoming parameters , the target action , the response type , and the response .

Figure 12.6. The user model contains a tutorials attribute that will hold the embedded array of tutorial dictionaries as JSON and an owner attribute that will hold the embedded username dictionary as JSON.

Figure 12.7. The edit-tutorial page contains two parameters and two requests.

Figure 12.8. The edit-tutorial request reference contains a description , the method and path of the request , the backend notes , incoming parameters , the target action , the response type , and the response .

Figure 12.9. Using async.each() allows you to iterate over the users to find the tutorial before executing User.update().

Figure 12.10. An illustration of embedding a record from one model into the record of another model. The user record’s username attribute is embedded in the tutorial record’s owner attribute . The entire tutorial record is embedded into the user record’s tutorials attribute .

Figure 12.11. An illustration of associating a record from one model into the record of another model. The user record’s id value is now referenced in the tutorial record’s owner attribute . The tutorial record’s id is now referenced in the user record’s tutorials attribute . Note that the user record’s tutorials attribute won’t be displayed as an array. To obtain what’s in the tutorials attribute, you’ll use the populate method later in this chapter.

Figure 12.12. Before the title of the tutorial record is changed, if you exchange the tutorials attribute reference for the actual tutorial record values, you get a title of “The best of Douglas Crockford on JavaScript.” After the title of the tutorial record is changed , exchanging the tutorials attribute reference for the actual tutorial record values will produce a title of “The best of Nikola Tesla on Electricity.” Thus, the reference remains in sync after changes.

Figure 12.13. The user model contains a tutorials attribute that configures a collection association between the user and tutorial models. The tutorial model contains an owner attribute that configures a model association between the tutorial and user models.

Figure 12.14. Before using .populate(), the user and tutorial records contain references whose value is the id of the associated record. After using .populate(), those references are populated with the tutorial record and the username attribute of the user record.

Figure 12.15. The username attribute is displayed, but the tutorials-detail page expects the createdAt and updatedAt attributes in the time-ago format .

Figure 12.16. The tutorials-detail page with the username , createdAt , and updatedAt properties formatted properly

Chapter 13. Ratings, followers, and search

Figure 13.1. The user model has a ratings association attribute configured as a collection with the rating model that uses via. The tutorial model has a ratings association attribute configured as a collection with the rating model that uses via. The rating model has a byUser association attribute configured as a model with the user. The rating model also has a byTutorial association attribute configured as a model with the tutorial.

Figure 13.2. The tutorials-detail page now displays the correct average rating and a rating for the currently authenticated user-agent , if any. In this case, the nikolatesla test user has provided a rating of three stars.

Figure 13.3. The tutorial model has a videos association attribute configured as a collection with the video model that uses via. The video model has a tutorialAssoc association attribute configured as a model with the tutorial model.

Figure 13.4. The create-video page displays attributes from the user, tutorial, and rating models.

Figure 13.5. The frontend expects these attribute formats in the create-video page.

Figure 13.6. The create-video page has some properties you’ll store in the video model, like title and src, and some properties that you’ll use to calculate the lengthInSeconds on the backend, like hours, minutes, and seconds.

Figure 13.7. Users can reorder the videos using the up and down sort order buttons B.

Figure 13.8. The video player page

Figure 13.9. The Follow Me and Unfollow Me buttons on the user profile toggle whether the user is following another user.

Figure 13.10. The three profile page configurations in authenticated and unauthenticated states

Figure 13.11. The search page has two different requests to the search-result endpoint that uses searchCriteria as an incoming parameter.

Figure 13.12. The browse-tutorial page contains a browse-results endpoint trigger by the Show More Tutorials button.

Chapter 14. Realtime with WebSockets

Figure 14.1. An HTTP request is necessary for a server to respond. The request creates an open connection between the client and server. Once the server responds, the connection is closed and another request is needed before the server can again respond to the client. With WebSockets, once a client makes a connection , the server can send a message to it , at any time so long as the connection remains open.

Figure 14.2. Each time Sails responds with a server-rendered view, a new WebSocket connection is created.

Figure 14.3. Making a GET request to localhost:1337/example/helloWorld returns the dictionary without the WebSocketId property and formatted as JSON.

Figure 14.4. Using io.socket.get() to make a request triggers a WebSocket virtual GET request that matches a blueprint action route and executes the helloWorld action that responds with the WebSocket id via the WebSocketId property.

Figure 14.5. The function signature of the get WebSocket method should look familiar to anyone who has used an AJAX equivalent method. The function takes the URL as the first argument and the callback as the second argument . Within the callback, you can get access to the response data as JSON and the JSON WebSocket Response dictionary .

Figure 14.6. Specific to chat, the video player page endpoint will pass seven locals to the view, four of which will be displayed: gravatarURL and username from the user model, message and created from the chat model. The view also contains a single outgoing parameter, message, sent within a request, Send chat.

Figure 14.7. A user can have many chats, but a chat can have only one user. A video record can have many chats, but a chat can have only one video.

Figure 14.8. The model associations between the user, video and chat models. The user model has a chats association attribute configured as a collection with the chat model that uses via . The video model has a chats association attribute configured as a collection with the chat model that uses via . The chat model has a sender association attribute configured as a model with the user . The chat model also has a video association attribute configured as a model with the video .

Figure 14.9. The broadcast method can be executed using the roomName, eventName, and data as arguments . The method can also be executed without the eventName , in which case the default event name is used. In addition, the currently requesting WebSocket can be omitted by adding the WebSocketToOmit argument, req. Finally, all four arguments can be used.

Figure 14.10. The significant interactions between the frontend and backend relating to chat

Figure 14.11. The chat event listener displays the chat event message contents.

Figure 14.12. The steps the frontend and backend must fulfill in order to display a message when a user is typing in the message field of the chat window

Figure 14.13. When two user-agents are on the same video and one begins to type a chat message , the other user-agent has a typing message displayed .

Chapter 15. Deployment, testing, and security

Figure 15.1. Horizontal scaling of an application on Heroku is accomplished by adding additional dynos to a given project.

Figure 15.2. Heroku’s load-balancing router automatically routes incoming requests to multiple instances of Sails within multiple dynos.

Figure 15.3. The Brushfire dynos will use a centralized PostgreSQL instance for the main database and a Redis instance for session and WebSocket storage.

Figure 15.4. You can rename an application directly from the Heroku dashboard under Settings .

Figure 15.5. Brushfire is stored in a local GIT repo on your local machine with two remote repos, one each on Heroku and GitHub .

Figure 15.6. Heroku offers a variety of add-ons as a service, including PostgreSQL. To obtain a list of all services, click Find More Add-ons on the dashboard.

Figure 15.7. Locate and click Heroku Postgres from the list of available services. Click Login to Install , and then Install Heroku Postgres . You’ll want to choose the zero-cost Hobby Dev and click the Provision button. Finally, you’ll see the Heroku Postgres instance .

Figure 15.8. When Heroku starts the dyno, an environment variable is created in memory . When Sails starts , how do you get the value of the environment variable into Sails?

Figure 15.9. The value of the DATABASE_URL contains the database credentials including the user , password , host , port , and database to the PostgreSQL instance.

Figure 15.10. Heroku allows you to create environment variables, which are set prior to launching Brushfire. When you create the PostgreSQL instance, the DATABASE_URL config var is also created.

Figure 15.11. The runtime mode dictates which database connection is used at startup.

Figure 15.12. Brushfire deployed and running on Heroku

Figure 15.13. The Mailgun credentials include the API key , the base URL , and the domain .

Figure 15.14. An overview view of the security vulnerabilities of Brushfire’s technical stack includes attacks related to the frontend, attacks related to the network, and attacks related to the backend.

Figure 15.15. After HTML escaping, the malicious script is rendered harmless before it’s injected into a page.

Figure 15.16. A typical CSRF attack begins with a user logging in to a trusted site. The user clicks a malicious link that appears to be legitimate but is actually a page on an untrusted site that uses the existing session to do bad things.

Figure 15.17. With CSRF protection enabled, a user logs in to a trusted site. The user clicks a malicious link that appears to be legitimate but is actually a page on an untrusted site that uses the existing session to attempt to do bad things. Without the csrf token, a malicious request will be rejected by goodGuys.com.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset