NAV navbar

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:

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:

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:

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:

  1. By being logged in as himself and updating its own information
  2. 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