For this technical assessment, I will be developing a fullstack project. This project is a prototype that solves problem statement defined below, written in Node.js with JavaScript, using Pug template engine, Express.js framework for backend, and basic data storage/manipulation with SQLite3.
This application is also deployed and hosted on Heroku.
- Problem Statement
- Proposed Solution
- Future Improvements
- Architecture
- Wireframe
- API Documentation
- Start the Application
- Setup Guide (Local)
- npm Commands
- Dependencies
Over the recent years, scams in Singapore are becoming more and more sophisticated. Such scams could come in many forms, but the common scams can be categorised under:
- Call scam
- These scams often involves the scammers trying to pry into the victims' personal/sensitive information in order to obtain access into their bank accounts.
- Job scam
- These scams often involves the scammers sending recruitment ads through messaging apps such as WhatsApp or Telegram, enticing unsuspecting victims by promising high pay with short working hours. The victims will be required to fork out their own money at some point in time, and eventually be rug pulled.
- Online Marketplace scam
- These scams often involves the scammers pretending to sell goods at a significantly cheaper price point than anywhere else. These scammers will not deliver the goods that the victims have paid for, and often they will go missing after the victim has paid.
- Donation scam
- These scams often involves the scammers pretending to be a part of charitable organisations and asking victims for donations.
Although there are campaigns which aims to educate Singaporeans on these common scams, it is simply one part of a deterrence and may not be enough to curb the sophisticated scams that scammers have been developing.
As such, I would like to propose IdentifyMe, a platform that allows individuals or business representatives to guarantee their identity during their exchange with the customers.
Individuals may apply for an identification profile by applying with MyInfo. Registered businesses will be able to apply for such identification profiles on behalf of their employees by applying with MyInfo Business. These applications will be instantly approved as their identities are guaranteed by MyInfo.
Similar to an NRIC or driver license, these identification profiles will be locally recognised as a legal form of identification. These identification profiles will be used for professional contexts, and will only contain information that is safe for the public to see.
Every identification profile will have a photo of the individual, an identification code, their full name, the entity that they are representing (if applicable).
The customer must be allowed to enter the identification code on the platform. The platform should display the exact same identification profile.
My solution aims to provide Singaporeans with a method to verify the identity of any individual or business representative in local context. On the other hand, these individuals or business representatives can safely assure their customers with ease, allowing better conversations to happen.
Due to time constraints, the proposed solution is only a bare-bone prototype. If time is not a constraint, I would suggest adding a dynamically generated QR code on the profile, where the customer can simply scan the QR code to be shown the same identification profile of the individuals or business representative on their own devices.
Simultaneously, this interaction will be logged under the customer's "contact history" to facilitate swift tracking of the individual or business representative when necessary. The customer will immediately receive a notification that the interaction with the individuals or business representative has been logged. Thus, if no notification was received after entering the code or scanning the QR, there is a chance that they might have encountered a fake identification profile.
The end goal for this would be to develop this solution as a native app for smartphones is recommended, as it will be trickier for scammers to attempt to replicate the platform in order to create a "phishing site" to fake their identification profiles.
This project is supported by a CI/CD pipeline workflow. The entire process starting from the moment the code is pushed onto this repository, to the deployment on Heroku, including build and publish of Docker image, are fully automated.
The CI/CD process begins with the workflow process on GitHub Actions. Click here to view the YAML file.
Upon pushing into the "main" branch, the CI workflow will be automatically triggered, beginning with checking out the repository so that the workflow can access it. The CI attempt to install all necessary dependencies and run the unit testings (test). When the tests are completed, it will export the code coverage report as an artifact, which can be downloaded from the "Summary" of each completed workflow process.
When the CI workflow passes, the codes will be pushed to Heroku automatically. Heroku will take instructions from heroku.yml, build and publish the docker image, and run the containerised application on a Heroku Dyno.
And that encompasses the entire continuous integration and deployment workflow of this app!
Unit tests have been written to test each of the app's URIs and API endpoints, ensuring that correct HTTP status code returns, all responses are returns intended and in proper format, and that exceptions are handled properly. In order to achieve this, I picked a combination of Mocha.js and Chai.js framework, which are commonly used together for writing Node.js unit testing. On top of these, it was also necessary to install Chai HTTP to support HTTP integration testing with Chai assertions.
To ensure that the unit tests are covering as many segments of the codes as possible, I have used a code coverage tester, Istanbul / nyc, as a means to ensure my codebase are well tested by my unit tests.
During the testing, the application automatically swaps to a test database, such that the production database will be left untouched. Seed data is created at the start of the test and dropped at the end of testing.
As mentioned in the Unit Test section, a code coverage report is generated at the end of the CI workflow, which can be downloaded from the workflow summary tab. A code coverage allowed me to write unit tests that covers all possible segments of the code, ensuring that all request and responses are submitted and returned as intended.
To avoid the common issue of an application failing to run on other devices, it is important to containerise the application by isolating the application into an image using Docker. The app will then run on the container, which contains an operating system and the supporting software. The script that instructs Docker on how to set up the app is located here.
Home Page
Verify Page
Verify Page (Not Found)
Profile Card
Login Page
Register Page
Register Page (Error)
MyInfo Registration
MyInfo Business Registration
Error Page
This API returns responses in JSON format. Please ensure that Content-Type: application/json
or
application/x-www-form-urlencoded
is set in the request header and request body conforms to either.
When a request is sent to an invalid endpoint, a 404
status code will be return with the following JSON response body:
{
"message": "API endpoint not found"
}
POST /api/createProfile
{
"code" : String,
"fullName" : String,
"sex" : String,
"race" : String,
"email" : String,
"nric" : String,
"entityName"? : String,
"UEN"? : String
}
Parameter | Type | Description |
---|---|---|
code |
string |
Required. Identification code that represents a person profile. Field must be UNIQUE. |
fullName |
string |
Required. Full name of the applicant. |
sex |
string |
Required. Gender of the applicant. |
race |
string |
Required. Race of the applicant. |
email |
string |
Required. Email of the applicant. |
nric |
string |
Required. NRIC of the applicant. |
entityName |
string |
Optional. Name of the entity that the applicant is representing. |
UEN |
string |
Optional. UEN of the entity that the applicant is representing. |
{
"id": Number
}
{}
GET /api/getProfile/:code
Parameter | Type | Description |
---|---|---|
:code |
String |
Required. Identification code that uniquely identifies a profile. |
{
"id": Number,
"code": String,
"fullName": String,
"sex": String,
"race": String,
"email": String,
"nric": String,
"entityName": String | null,
"UEN": String | null
}
{}
Choose only one of the two following methods below to start the app. Once the app is running, you should be able to
access it via http://localhost:3000
. If you have issues with starting or running the app, please refer to the
setup guide below.
A batch and shell script has been created for your convenience. The script will attempt to set any process environment
variables before executing npm start
. Remember to run npm ci
in the before executing the batch or shell script. You
may find out more about the npm commands here.
You may simply execute the following command to start the application:
.\start.bat
./start.sh
NOTE: Uncomment the
PORT
in the batch or shell script to set the application to run on an available port of your choice.
Execute the two following commands, one after another:
# docker build -t <name_for_image> .
docker build -t ndi-tap-2023 .
# docker run -d -p <Browser_expose_port>:<application port> <image_id/name>
docker run --env URI_IGNORE_PORT=0 -d -p 3000:3000 ndi-tap-2023
# sudo docker build -t <name_for_image> .
sudo docker build -t ndi-tap-2023 .
# sudo docker run -d -p <Browser_expose_port>:<application port> <image_id/name>
sudo docker run --env URI_IGNORE_PORT=0 -d -p 3000:3000 ndi-tap-2023
Needless to say, please clone the project first before proceeding.
To run this project locally, you must have Node.js installed on your device. A quick way to get started is to directly install Node.js on your system. You may download the installer here.
However, it is strongly recommended to use a Node version manager such as nvm to install Node.js and npm. You may read more about it here.
You may choose to containerise this application and run it on Docker. To do so, download and install Docker here.
Before running the app and unit test, you may configure the following optional process environment variable(s).
URI_IGNORE_PORT=0
ENV=test
PORT=3000
Parameters | Type | Description |
---|---|---|
URI_IGNORE_PORT |
Number |
Required. Set 1 if the port is not required. By default, you should set to 0 when running locally or when the port is necessary. Live deployment may require this value to be set to 1 . |
ENV |
String |
Optional. Set value to test for unit testing only. |
PORT |
Number |
Optional. Specify the port that your app will run on. By default, it is set to 3000. |
Here, we have a list of CLI commands that may be useful to you. In the project directory, simply open a terminal to run the following:
- This command will install all necessary dependencies based on the
package-lock.json
. - You only need to run this command once in the project directory.
- This command starts the node server located in the root of the repository.
- Alternatively, you may run
node app.js
. - You can now access the app through
localhost:3000
.
- This command starts the node server in development mode using nodemon.
- It will monitor for any changes to the JavaScript files and restart the node server when changes are observed.
- Alternatively, you may run
nodemon app.js
- For more info, please refer to Nodemon.
- This command runs the unit tests and generates a code coverage report.
- Code coverage report are generated in
./coverage
, in HTML format for ease of readability. - Alternatively, you may run
nyc --reporter=html --reporter=text mocha test/test.js --exit"
- For more info, you may read up on them in their official documentations: Istanbul / nyc , Mocha.js, Chai.js.
Refer to package-lock.json for all the nitty-gritty details, or refer to the following for a summarised list taken from package.json:
"dependencies": {
"body-parser": "^1.20.0",
"bootstrap": "^5.2.0",
"cors": "^2.8.5",
"express": "^4.18.1",
"jquery": "^3.6.0",
"knex": "^2.2.0",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"pug": "^3.0.2",
"random-name": "^0.1.2",
"randomstring": "^1.2.2",
"request": "^2.88.2",
"sqlite3": "^5.0.11"
},
"devDependencies": {
"chai": "^4.3.6",
"chai-http": "^4.3.0",
"mocha": "^10.0.0",
"nodemon": "^2.0.19",
"nyc": "^15.1.0"
}