Introduction
Following the instructions from the repo, you can access the local API at:
localhost:5000
Welcome to the Comms4Docs API! This is a Node.js based API used as a backend system for the Comms4Docs Mobile App as well as for the Comms4Docs Admin Panel.
You can find the production API hosted at https://api.comms4docs.co.uk and if you wish to run it locally, the source code can be found in this repository (permission to access required)
Authentication
Example of a successfully set auth cookie:
{
_id: "****",
expires: 2020-09-20T09:59:59.482+00:00,
session: "{"cookie":{"originalMaxAge":86400000,"expires":"2020-09-20T09:54:00.592Z","httpOnly":true,"path":"/","sameSite":false},"passport":{"user":"****"}}
}
Comms4Docs uses 2 types of authentication:
- Email and Password for the app users
- 2 Factor Authentication for the admin panel (Email, Password, 6-digit phone code)
Both of the auth systems are based on Passport.js in conjunction with the local strategy.
Each Session is maintained via an unique Cookie Token generated by Passport which is based on serializing and deserializing the according user. In order to maintain multiple sessions in tandem, they are stored securely in the database, not in the Local Storage.
Each auth cookie lifetime is set for a year.
The 2 Factor Authentication is made possible using 3 levels of encryption:
- QR Code Token (generated when a user creates an account)
- an ascii token (generated once the admin user confirms the email and password)
- a 6-digit phone code (generated by scanning the qr code)
Users
User Model
Field | Data Type | Required |
---|---|---|
_id |
ID | ✔️ |
name |
String | ✔️ |
email |
String | ✔️ |
password |
String | ✔️ |
tts_accent |
Object | |
usage_history |
Number | |
subscription |
String | ✔️ |
last_active |
Date | |
grade |
String | ✔️ |
speciality |
String | ✔️ |
fav_phrases |
Array | |
role |
String | |
secret |
Object |
fav_phrase Model
Field | Data Type | Required |
---|---|---|
category_id |
ID | ✔️ |
body |
String | ✔️ |
subscription enum
Value |
---|
free |
premium |
DEFAULT: 'free'
role enum
Value |
---|
customer |
admin |
DEFAULT: 'customer'
usage history
DEFAULT: '0' (seconds)
Create a new user
POST /users/register
{
"name": "Gavin Belson",
"email": "gavinbelson@gmail.com",
"password": "********",
"grade": "4",
"speciality": "Internal medicine"
}
Response
{
"message": "Success! New user created.",
"user": {
"usage_history": 0,
"subscription": "free",
"role": "customer",
"_id": "5f67bc4097e4c843b00b9bf7",
"name": "Gavin Belson",
"email": "gavinbelson@gmail.com",
"password": "$2a$10$ZrZAz0pCG9LmqgESWwQaRuSkrUopmS9Hj4mgO2l4Yy6Qf45ABIwFG",
"tts_accent": {
"label": "English - United Kingdom",
"value": "en-GB"
},
"last_active": "2020-09-20T20:32:00.715Z",
"grade": "4",
"speciality": "Internal medicine",
"secret": {
"ascii": ".!4Isx#^qIN#l{%cQ8v,m<awDySrWywX",
"otpauth_url": "otpauth://totp/Comms4Docs?secret=FYQTISLTPARV44KJJYRWY6ZFMNITQ5RMNU6GC52EPFJXEV3ZO5MA"
},
"fav_phrases": [],
"createdAt": "2020-09-20T20:32:00.720Z",
"updatedAt": "2020-09-20T20:32:00.720Z",
"__v": 0
}
}
Access: Everyone
User creation requires a POST
request with a body passed that contains a minimum of all required fields in the model.
A newly created user will automatically have assigned british accent, 0 usage history, customer role, free subscription, last active date, an ascii secret and a qr code url that can be used within an <img>
tag.
Validation
User
name
- alphanumeric value email
- unique alphanumeric with @ and . password
- min length 6, at least one lower case and an uppercase usage_history
- integers subscription
- enum value grade
- alphanumerics and underscore role
- enum value
The password will be hashed and salted, and is never stored in plain text.
Fav Phrase
category_id
- valid category id
Authenticate a user
POST /auth/login
{
"email": "admin_email@gmail.com",
"password": "******"
}
Response
{
"message": "Success! Credentials are correct.",
"user": {
"_id": "******",
"secret": "**********"
}
}
POST /auth/verify-token
{
"_id": "******",
"secret": "**********",
"token": "086706"
}
Response
{
"message": "Success! Logged in as admin.",
"admin": {
}
}
GET /auth/check-session
{
"message": "Success! Session found!",
"user": {
}
}
GET /auth/logout
{
"message": "Success! You've been logged out."
}
Access: Everyone
As mentioned in the Authentication, there are 2 user types that can authenticate: admin and customer.
Admin
If the account if of an admin type, the user must go through 2 levels of authentication: Email & Password and 6-Digit Phone Code. The Token will not be created unless both requirements are passed.
Once the first level of encryption is passed, the user id and a secret are sent as a response, both being needed in the next level.
The second level of encryption is what will create and store the Token in the database. In order to pass 3 things are needed:
- User ID
- Secret
- Phone Code
The Phone Code is generated using an Authenticator app on Android or iOS by firstly generating a QR Code with the link passed on user registration and then scanning it in the app. The 6 digit code is refreshed once at 30 seconds.
Customer
Customers are logged in from the clients using only their Email and Password. If both are correct, a session token is generated and stored in the database for one year. After that, a re-login is necessary.
Session Check
To check if there is a session for the current user, a simple POST
request to the server will suffice.
Logout
Once a logout request is made, the server will search for the current session, will destroy it and also delete the record from the database.
Get all users
GET /users/
{
"message": "Success! All users fetched!",
"users": [
{},
{},
{}
]
}
Access: Admin
Query to get information about all the users stored in the database.
The results are sent as an array of objects.
Get last week users
GET /users/lastweek
{
"stats": [
{
"date": "2020-09-14T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-15T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-16T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-17T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-18T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-19T23:00:00.000Z",
"users": 4
},
{
"date": "2020-09-20T23:00:00.000Z",
"users": 5
}
]
}
Access: Admin
Shows the day-to-day total number of users registered between today and 7 days ago.
This can be useful for showing a growth graph in the client side.
An array of objects is sent back, each object containing a date and the total number of users from that date.
Get a specific user
GET /users/:id
{
"message": "Success! Single Phrase fetched!",
"user": {
"usage_history": 39997,
"subscription": "free",
"role": "customer",
"_id": "******",
"fav_phrases": [
{
"_id": "******",
"category_id": "******",
"body": "So, in a typical week how often do you go out to the pub would you say?",
"createdAt": "2020-08-10T17:56:49.297Z",
"updatedAt": "2020-08-30T09:48:17.724Z"
}
],
"name": "App user 1",
"email": "appuser@gmail.com",
"last_active": "2020-08-30T10:56:21.990Z",
"password": "******",
"grade": "5",
"speciality": "Internal Medicine",
"tts_accent": {
"label": "English - United Kingdom",
"value": "en-GB"
},
"secret": {
"ascii": "******",
"otpauth_url": "******"
},
"createdAt": "2020-08-05T11:53:12.213Z",
"updatedAt": "2020-08-30T10:56:22.945Z",
"__v": 495
}
}
Access: Admin
Get all the stored information about a specific user.
In order for a request to be successful, a valid stored user id must be added as a parameter.
Each user is stored and sent back as an object type.
Update a specific user
PUT /users/:id
{
"name": "John Doe"
}
Response
{
"message": "Success! User updated",
"user": {
}
}
Access: User
There are 2 ways a user can be updated:
- By being logged in as himself and updating its own information
- Information is being updated by an Admin
Each field can be updated individually so it is only necessary to be passed a single field in order for a user to be updated.
In order for a request to be successful, a valid stored user id must be added as a parameter.
To add or remove a phrase from favorites, jump to this chapter.
Delete a specific user
DELETE /users/:id
{
"message": "Success! User deleted!"
}
Access: User
Deleting a specific user means that the record associated to that user id will be forever deleted from the database.
With this action, all the saved favorite Phrases will be deleted.
If the user is currently logged in, first, the session stored in the database is deleted and only then the whole user object.
In order for a request to be successful, a valid stored user id must be added as a parameter.
Any logged in admin is also empowered to delete all the information about any user.
Categories
Category Model
Field | Data Type | Required |
---|---|---|
_id |
ID | ✔️ |
title |
String | ✔️ |
subscription |
String | ✔️ |
conversation_image |
String | |
conversation_audio |
String | |
conversation_body |
String |
title must be unique.
subscription enum
Value |
---|
free |
premium |
DEFAULT: 'free'
Create a new category
POST /categories/
formdata
name: 'Test Category'
subscription: 'Premium'
conversation_body: 'Conversation text for this category'
conversation_image: hello.jpg
conversation_audio: test.mp3
Response
{
"message": "Success! New Category Created",
"category": {
"subscription": "premium",
"_id": "5f692204acf8ec2c708e250f",
"title": "Test Category",
"conversation_body": "Conversation text for this category",
"createdAt": "2020-09-21T21:58:28.692Z",
"updatedAt": "2020-09-21T21:58:28.692Z",
"__v": 0
}
}
Access: Admin
Categories can be created using a POST
request with a body that contains the required fields. They are the main way of filtering the newly created phrases.
Since there are 2 types of customers free & premium, the categories can be accessed by either one of them depending on the preference of the Admin. Free
means being accessed by everyone and premium
can only be accessed by the paid users.
If a category is premium
all the phrases associated with it are by definition premium as well.
Each category has an optional
conversation associated to it. A conversation is formed of an image, and audio file and written transcript. Therefore, conversation_audio
& conversation_body
are fields that accept a file.
Validation
Category
title
- unique subscription
- enum value conversation_audio
- Audio formats: mpeg, ogg, wav, x-metroska, mp4, max file size: 50 mb conversation_image
- Image formats: jpg, gif, png, max file size: 5 mb
All the files are store in /upload
using a naming convention: conversation_audio-randomID
or conversation_image-randomID
Get all categories
GET /categories/
{
"message": "Success! Categories fetched!",
"categories": [
{},
{},
{}
]
}
Access: User
Query to retrieve all the categories stored in the database.
The results are sent as an array of objects.
Get a specific category
GET /categories/:id
{
"message": "Success! Category fetched!",
"category": {
"subscription": "premium",
"_id": "5f692204acf8ec2c708e250f",
"title": "Test Category",
"conversation_body": "Conversation text for this category",
"createdAt": "2020-09-21T21:58:28.692Z",
"updatedAt": "2020-09-21T21:58:28.692Z",
"__v": 0
}
}
Access: User
Query to retrieve a specific category from the database.
The result is sent as an object.
The id parameter in the URL must be a valid existing id, otherwise an explicit error will be thrown.
Update a specific category
PUT /categories/:id
formdata
subscription: 'free'
Response
{
"message": "Success! Category updated",
"category": {
"subscription": "free",
"_id": "5f692204acf8ec2c708e250f",
"title": "Test Category",
"conversation_body": "Conversation text for this category",
"createdAt": "2020-09-21T21:58:28.692Z",
"updatedAt": "2020-09-21T22:17:37.414Z",
"__v": 0
}
}
Access: Admin
Using a PUT
request each category can be updated. All the fields are checked individually, and there is no need to pass an entire category inside of the body, just the updated fields.
The id parameter in the URL must be a valid existing id, otherwise an explicit error will be sent back.
In the case where a file is updated by an Admin, (i.e. conversation_image, conversation_audio), the system first searches for that specific file in the /uploads
folder, deletes the record, adds the new file with the new name and updates the database with the new path. This way the old files won't occupy unnecessary server space.
All updated fields sent in the body must comply with the Validation rules set in the Model / Controller.
Validation
title
- unique subscription
- enum value conversation_audio
- Audio formats: mpeg, ogg, wav, x-metroska, mp4, max file size: 50 mb conversation_image
- Image formats: jpg, gif, png, max file size: 5 mb
Delete a specific category
DELETE /category/:id
{
"message": "Success! Category deleted & all the corresponding phrases."
}
Access: Admin
Deleting a specific category means that the record associated to that category id will be forever deleted from the database.
With this action, all the phrases associated with that category are deleted.
Also, all the files in the /uploads
associated with that category are deleted.
In order for a request to be successful, a valid stored user id must be added as a parameter.
Phrases
Phrase Model
Field | Data Type | Required |
---|---|---|
_id |
ID | ✔️ |
category_id |
ID | ✔️ |
body |
String | ✔️ |
Create a new phrase
POST /phrases/
{
"category_id": "5f0ef4250856267a36ecab32",
"body: "Phrase 1 from category 1"
}
Response
{
"message": "Success! New phrase created",
"phrase": {
"_id": "5f6939c6acf8ec2c708e2510",
"category_id": "5f327d86cc3d4a7aec85c939",
"body": "Phrase 2 from category 1",
"createdAt": "2020-09-21T23:39:50.033Z",
"updatedAt": "2020-09-21T23:39:50.033Z",
"__v": 0
}
}
Although the structure is very simplistic, a phrase is the core piece of content inside the Comms4Docs API.
To create a phrase it is only necessary to pass a body
inside the POST
request and a valid category_id
which will be associated to.
Validation
In the case of phrases, the validation is formed only from the types of fields passed.
category_id
- unique & valid body
- String
Get all phrases
GET /phrases/
{
"message": "Success! All Phrases fetched!",
"phrases": [
{},
{},
{}
]
}
Access: User
This query will retrieve all the phrases in the database in no particular order. This is a good method of keeping track of the number of the total phrases in the system, however it is not recommended to heavily use this query as it is memory heavy.
The results are sent as an array of objects.
Get all phrases by category
GET /phrases/category/:id
{
"message": "Success! Phrases by category fetched!",
"phrases": [
{},
{},
{}
]
}
Access: User
This is the main way of consuming the phrases based on their category.
Once the category id is known, all the phrases in that category can be queried and used in a client.
The id parameter in the URL must be a valid existing category_id, otherwise an explicit error will be thrown.
Get a specific phrase
GET /phrases/:id
{
"message": "Success! Single Phrase fetched!",
"phrase": {
"_id": "5f318a41cc3d4a7aec85c91c",
"category_id": "5f31775ecc3d4a7aec85c8df",
"body": "Do you drink at all?",
"createdAt": "2020-08-10T17:56:17.900Z",
"updatedAt": "2020-08-10T17:56:17.900Z",
"__v": 0
}
}
Access: User
Simple query to retrieve all the information about a single phrase. This might me useful in a scenario where a phrase has its own client-side display page.
A category_id
is not needed, just the _id
of the actual phrase.
The id parameter in the URL must be associated to valid existing phrase, otherwise an explicit error will be thrown.
Add a phrase to favorites
PUT /users/:id
{
"add_fav_phrase": "5f32bc4fcc3d4a7aec85c94b"
}
Response
{
"message": "Success! User updated",
"user": {
}
}
Access: User
Adding a Phrase to favorites is actually part of the updating a User
process.
When a new user creates an account, a field called fav_phrases
is automatically created and consists of an empty array.
The process of adding a Phrase to favorites is nothing else then just adding phrases objects to the said array.
In order to have a 200 response, both ids must be valid and exiting user/phrase ids. Otherwise, an error will be sent back.
Remove a phrase from favorites
PUT /users/:id
{
"rm_fav_phrase": "5f32bc4fcc3d4a7aec85c94b"
}
Response
{
"message": "Success! User updated",
"user": {
}
}
Access: User
Removing a phrase from the favorites is exact same process as adding to favorites, but in reverse.
A PUT
request with the logged in user id as parameter
is necessary to perform a phrase remove from favorites, which will update the specific user array of favorite phrases.
You can read a more in depth explanation here
Update a specific phrase
PUT /phrases/:id
{
"body": "Updated phrase body"
}
Response
{
"message": "Success! Phrase updated",
"phrase": {
"_id": "5f6939c6acf8ec2c708e2510",
"category_id": "5f327d86cc3d4a7aec85c939",
"body": "Updated phrase content",
"createdAt": "2020-09-21T23:39:50.033Z",
"updatedAt": "2020-09-22T03:01:37.397Z",
"__v": 0
}
}
Access: Admin
This query updates the body of a phrase.
It is an action that can only be performed by admin users as it is main core piece of the content.
The id parameter in the URL must be associated to valid existing phrase, otherwise an explicit error will be thrown.
Delete a specific phrase
DELETE /phrases/:id
{
"message": "Success! Phrase deleted."
}
Access: Admin
Deleting a specific phrase means that the record associated to that phrase_id will be forever deleted from the database.
This also means that the phrase will be deleted from the category that it is a part from.
In order for a request to be successful, a valid stored user id must be added as a parameter.
FAQs
FAQ Model
Field | Data Type | Required |
---|---|---|
_id |
ID | ✔️ |
question |
String | ✔️ |
answer |
String | ✔️ |
Validation rules are only set by the field types and no other additional rules are set.
Create a new question
POST /faqs/
{
"question": "How frequently is the content updated?",
"answer": "Weekly"
}
Response
{
"message": "Success! New question created.",
"question": {
"_id": "5f696c4988e1d24316ba64af",
"question": "How frequently is the content updated?",
"answer": "Weekly",
"__v": 0
}
}
Access: Admin
FAQs
or Frequently Asked Questions
are a list of posts meant to help the users from the clients understand the platform better without the contacting the Admin.
To add question
, a simple POST
request containing the question and the answer will suffice.
For a successful request, valid strings must be passed.
Get all the questions
GET /faqs/
{
"message": "Success! Questions and Answers fetched!",
"questions": [
{},
{},
{}
]
}
Access: User
Using this query, a client will be able to show all the FAQs to the customers.
The results are sent as an array of objects.
Get a specific question
GET /faqs/:id
{
"message": "Success! Question fetched!",
"question": {
"_id": "5f2ed413cc3d4a7aec85c8d5",
"question": "Test question 1?",
"answer": "Test answer 1.",
"__v": 0
}
}
Access: User
This query can retrieve each question individually, as an object.
This can be useful if the client's functionality requires a personalised page for each question.
Update a specific question
PUT /faqs/:id
{
"question": "Updated question?"
}
> response
```JSON
{
"message": "Success! Question updated",
"question": {
"_id": "5f2ed413cc3d4a7aec85c8d5",
"question": "Updated question?",
"answer": "Test answer 1.",
"__v": 0
}
}
Access: Admin
An FAQ can be updated using a PUT
request.
The body sent can be either question
or answer
or both if updating both the question and the answer. Passing them separately, will update only that value.
Delete a specific question
DELETE /faqs/:id
{
"message": "Success! Question deleted!",
"question": {
"_id": "5f2ed413cc3d4a7aec85c8d5",
"question": "Updated questoin?",
"answer": "Test answer 1.",
"__v": 0
}
}
Access: Admin
Deleting a question will permanently remove the record of the question from the database.
A successful DELETE
request implies removing both the question and the answer.
The id parameter in the URL must be a valid existing faqs_id, otherwise an explicit error will be thrown.
Errors
The Comms4Docs API uses the following error codes:
Code | Meaning |
---|---|
401 | Unauthorized - You are not logged in as Admin. |
404 | Not Found -- The specified URL could not be found. |
429 | Too Many Requests |
500 | Internal Server Error |