My site has successful cloud deployment and can be available at: https://janusjobs.herokuapp.com/
Furthermore, for an online version of this README document please visit:
https://janusjobs.herokuapp.com/README.html
JanusJobs is a single page job advertising site, allowing users to sign in with their Google Accounts and advertise their jobs on a message-board like site. These advertisements will have only crucial information such as employer, title, and job description, with profile images and a link to the employer's site or primary advertisement when clicked. The API uses GET and POST requests to handle the different entities.
The site is running on herokuapp at: https://janusjobs.herokuapp.com/. To test locally: you would need to generate your own CLIENT_SECRET in a .env file, the port that would be in use is port 8090.
*Note: This is a summary, for more detailed documentation on the various methods used please visit:
https://janusjobs.herokuapp.com/ssindex.html*
All of my fetch statements are encapsulated in the form:
try {
// fetch code
} catch (err) {
alert(err)
}
This ensures that if the server is terminated whilst the site is in use, it handles the server degradation gracefully whilst also alerting the user of the problem.
server.js contains only the following code snippet:
const app = require("./app");
app.listen(process.env.PORT||8090);
Therefore server.js is only listening for either the port assigned to it by the external host, in my present application this port is provided by herokuapp, or the local port 8090.
The file first requires an initial setup with the following code:
let express = require("express");
let myParser = require("body-parser");
let app = express();
Following this, in app.js I create the lists that will store the several different separate entities when required. A REST API provides each entity with appropriate GET/POST meoth.ds These lists are:
let empList = []; //stores employers
let descriptions = []; //stores job descriptions
let jobList = []; //stores job titles
let linkList = []; //stores URLs to jobs
let imgList = []; //stores profile images
See the comments above for what each of the lists stores. There are a few additional key points about the lists that I will also mention: the empList may contain google user profile names instead of inputted employer names with "(verified)" attached, the imgList contains either elements from google or generated by the random image generator in index.js, the link list has been checked using regex for the URLs having correct format, and finally the descriptions have been checked to make sure they are not too long before submission.
A summary of the GET/POST requests:
These are described in more detail in the full server-side code documentation (available above or through the About Janus section).
In brief:
GET /:
Attempts a general get request from the webpage.
GET /descList, /empList, /jobList, /linkList:
These are all entities submitted in the same form on the webpage, they are also posted at the same time. See the comments in the code above to see the content of each list.
GET /imgList:
The images in this list are supplied by the profile information from the Google API. They are the profile images accompanying each post, if no user is signed in then a random profile image is used as described in the client-side section of the documentations.
POST /add:
This POST, posts all of the above lists at the same time so that information regarding the same job are in the same index of each list.
Along with GET requests from the GOOGLE OAUTH API, I use passport to create sessions for the site, meaning that different users can be logged on to the site with their separate google accounts at the same time:
let passport = require("passport");
let GoogleStrategy = require("passport-google-oauth20").Strategy;
// User sessions control via Serialize and Deserialize
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
// Session control with ClientID and clientSecret (Authorisation)
passport.use(new GoogleStrategy({
clientID: "1042353776096-b40nc822i1clrtc12gc7tiu3g57hin85.apps.googleusercontent.com",
clientSecret: process.env.CLIENT_SECRET,
callbackURL: process.env.CALLBACKURL
// callbackURL: "localhost:8090" //for use with local testing
},
function (accessToken, refreshToken, profile, done) {
return done(null, {user:profile, accessToken:accessToken, refreshToken:refreshToken});
}
));
// Initialising Passport Session
app.use(passport.initialize());
app.use(passport.session());
The code above then works with the following two GET requests to provide Google account login and authentication with integrated sessions:
/** Attempts a GET request from GOOGLE OAUTH API through passport
* @name GET /auth/google
* @path {GET} /auth/google
* @code {302} If profile found
*/
app.get("/auth/google",
passport.authenticate("google", { scope: ["profile"] }),
function (req, res) {
res.status(302);
});
/** Attempts a GET request from GOOGLE OAUTH API Callback through passport
* @name GET /auth/google/callback
* @path {GET} /auth/google/callback
* @code {302} If redirect found
*/
app.get("/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/" }),
function (req, res) {
res.redirect("/"); // redirecting page
res.status(302);
});
The first of these GET requests returns user profile information, whereas the second occurs when call back and therefore a redirect back to the page is necessary.
I have a couple of different variables in a .env file (this has not been submitted), these are:
CLIENT_SECRET
CALLBACKURL
I have made the Environment Variable CALLBACKURL
so that whilst the site is online at herokuapp, the callbackURL returned in the Google Oauth 2.0 authentication works with both the online site as well as with localhost when using port 8090.
The variable CLIENT_SECRET
should be kept private for security reasons and therefore I have not included the .env file in either this submission nor on my Github repository. To circumvent this, if the site was to be tested not through herokuapp but rather through localhost then the tester would have to create their own Client Secret with the Google Dev Tools.
In order to integrate these variables into my site, I first had to install the dotenv
package, and then setup my app.js with the following line:
require("dotenv").config();
With dotenv
configured, I can now use the following code:
passport.use(new GoogleStrategy({
clientID: "1042353776096-b40nc822i1clrtc12gc7tiu3g57hin85.apps.googleusercontent.com",
clientSecret: process.env.CLIENT_SECRET,
callbackURL: process.env.CALLBACKURL
// callbackURL: "localhost:8090" //for use with local testing
},
function (accessToken, refreshToken, profile, done) {
return done(null, {user:profile, accessToken:accessToken, refreshToken:refreshToken});
}
));
Where the clientSecret and callbackURL are linked to process.env
instead of explicitely stating the secret and the URL.
To summarise, I have implemented the chosen Environment Variables above in order to provide additional security when logging in to my site, as well as enabling localhost testing without the need to change any of the source code provided.
This file ensures that all required dependencies are supplied on:
npm install
The dependencies required are as follows:
"dependencies": {
"body-parser": "^1.18.3",
"dotenv": "^7.0.0",
"express": "^4.16.4",
"express-session": "^1.16.1",
"ionicons": "^4.5.6",
"jest": "^24.7.1",
"jquery": "^3.4.0",
"passport": "^0.4.0",
"passport-google-oauth20": "^2.0.0",
"uuidv4": "^4.0.0"
},
With the following devDependencies used (for more information on the eslint testing used please see .eslintrc.js):
"devDependencies": {
"eslint": "^5.16.0",
"jsdoc": "^3.5.5",
"jsdoc-http-plugin": "^0.3.0",
"supertest": "^4.0.2"
}
Furthermore, I have used the scripts below to ensure that the two following commands aer executed correctly:
npm start
npm test
Here are the scripts used:
"scripts": {
"pretest": "eslint app.js server.js --fix",
"test": "jest --coverage",
"start": "node server.js",
"dev": "nodemon index.js"
},
* Note: In this assignment full client-side documentation and code explanation was not required. It was suggested that if included the documentation should just include details of client-side use and workings. *
I have validated the html of index.html
as required. I would like to note however that if a html validator is ran on global.html
, app.js.html
, or ssindex.html
, they will results in some HTML errors due to the way they are constructed. They are created automatically from comments in app.js
and work within one another along with relevant scripts, as a result HTML errors will be returned in the validator but the output will be as desired.
When the page initially loads, the page will send GET requests for entities relating to job postings, if this request returns empty lists then there will be a message telling the viewer that no jobs have been posted yet and they will be encouraged to post the first.
Once jobs have been successfully fetched, job postings will display on the page, these posting will reveal a job description when hovered over (or tapped on if on mobile). If during the creation of the job the user was signed in to their Google account, then their profile image will accompany the posting. Otherwise, a random profile image (a randomly coloured square) will appear instead as a default.
When posting a job, the user will need to input several details: job title, employer, job description, and a link to their page. Upon clicking "Submit", index.js will first check that no inputs were left blank and the URL is in a valid form. The only exception to this check is when a user is signed in, the employer name will automatically be set to their own account name, this is only a placeholder and can be changed however otherwise it will be used when the job has been posted.
In the top left of the navbar there is an "About Janus" button, this will lead to an expanding side bar giving a little information about the site and also containing links to the documentation. As the information section expands, the size of any posted jobs will reduce to accommodate.
In the top right of the navbar there are two buttons: "Post a Job" and "Sign in", the use of the job posting button is detailed in the section above. The Google sign in button will allow the user to sign into their google account, once signed in, a login message will appear under the Janus logo and the employer information will be auto filled when posting a job. Furthermore, if logged in the "Sign in" button will be replaced with a "Sign Out" to reverse the process.
All signing in is incorporated with sessions so that if a user was to sign in to their account from one location, all locations accessing the site would not also be logged on to their account. For further details please see the Server-Side documentation.
The navbar, excluding the logo and collapsible menu button, is sticky, meaning that once enough entries are on the page that scrolling is required, the buttons and search bar will remain at the top of the visible page.
The search bar allows the user to search for jobs with any keywords or strings within the employer name or the job title. Clicking "Search All", will reveal the search results. Clicking "Search All" again when nothing is in the search bar will go back to the homepage and show all of the jobs that have been posted.
The webpage is designed such that no matter the size of the viewport the page should be easily readable by the user. As the screen size reduces the navbar slowly folds in on itself until a predetermined viewport size limit is reached. At this point, the search options and buttons in the navbar will disappear automatically and can be expanded again either manually by pressing the expand burger menu button or automatically by increasing the viewport size again.
Other elements also change with viewport size, for example, the width of posted jobs, and the size of the logo in the header to an extent.
The source code contains:
app.test.js
and
.eslintrc.js
These can be run with npm start
. As required the .eslintrc.js
file is a pretest and operates on app.js
and server.js
, returning no errors with the extensive rules provided in the eslintrc.js
specification. I should note that whilst a large number of rules were used, I did turn off the following for various reasons:
"no-console": "off",
"no-useless-escape": "off",
By removing no-console
, it is easier to test the workings and functionality of my code without incurring eslint errors or warnings.
no-useless-escape
had to be removed due to some regex in my code testing the form of inputted URLs, eslint recognised some of the end characters of the regex test as useless where in fact they were necessary to validate the url.
As for app.test.js
, my code has several tests which are all passed and explained in further detail in the link at the top of the server-side documentation section of this README.md. The testing uses a variety of HTTP codes and also includes checking content-type. The test should return good coverage.
The accounts system for the site is handled by the external Google Oauth 2.0 API, allowing users to log into their google accounts, the data from these accounts are then used in several cases in the site.
Firstly, once a user has logged in, the google sign in button will be replaced with a sign out button and there will be a "Logged in as" message underneath the site logo. Authentication has been used so that next to this logged in message there will be a "verified" signal in brackets next to the posting if an ID Token has been received.
Account name will also be auto-filled in the form for job posting and the account image will accompany the job automatically once on the screen.
In summary, the external API provides the site with a secure method of logging into an already established account, whose name, profile picture, and an ID token are then passed to the site.
When hovering over a posted job, the job should expand to reveal the job description, however occasionally the most recent posting will show the description as undefined. If jobs are continued to be posted after this, the description will later appear correctly after further jobs have been posted. Similarly, if the page is refreshed the description appears correctly also.
When using herokuapp for hosting, the data isn't persistent meaning any jobs posted will be lost when the server is reset, the jobs will remain posted however if testing using localhost port 8090.