diff --git a/README.md b/README.md
index e3661576..c7529084 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,14 @@ The Mentoring building block enables effective mentoring interactions between me
+# System Requirements
+
+- **Operating System:** Ubuntu 22
+- **Node.js:** v20
+- **PostgreSQL:** 16
+- **Citus:** 12.1
+- **Apache Kafka:** 3.5.0
+
# Setup Options
Elevate notification services can be setup in local using two methods:
@@ -108,75 +116,286 @@ Elevate notification services can be setup in local using two methods:
**Expectation**: Run single service with existing local dependencies in host (**Non-Docker Implementation**).
-### Steps
+## Installations
+
+### Install Node.js LTS
+
+Refer to the [NodeSource distributions installation scripts](https://github.com/nodesource/distributions#installation-scripts) for Node.js installation.
+
+```bash
+$ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\
+sudo apt-get install -y nodejs
+```
+
+### Install Build Essential
-1. Install required tools & dependencies
+```bash
+$ sudo apt-get install build-essential
+```
- Install any IDE (eg: VScode)
+### Install Kafka
- Install Nodejs: https://nodejs.org/en/download/
+Refer to [Kafka Ubuntu 22.04 setup guide](https://www.fosstechnix.com/install-apache-kafka-on-ubuntu-22-04-lts/)
-2. Clone the **Notification service** repository.
+1. Install OpenJDK 11:
+ ```bash
+ $ sudo apt install openjdk-11-jdk
```
- git clone https://github.com/ELEVATE-Project/notification.git
+
+2. Download and extract Kafka:
+
+ ```bash
+ $ sudo wget https://downloads.apache.org/kafka/3.5.0/kafka_2.12-3.5.0.tgz
+ $ sudo tar xzf kafka_2.12-3.5.0.tgz
+ $ sudo mv kafka_2.12-3.5.0 /opt/kafka
```
-3. Add **.env** file to the project directory
+3. Configure Zookeeper:
+
+ ```bash
+ $ sudo nano /etc/systemd/system/zookeeper.service
+ ```
- Create a **.env** file in **src** directory of the project and copy these environment variables into it.
+ Paste the following lines into the `zookeeper.service` file:
+ ```ini
+ /etc/systemd/system/zookeeper.service
+ [Unit]
+ Description=Apache Zookeeper service
+ Documentation=http://zookeeper.apache.org
+ Requires=network.target remote-fs.target
+ After=network.target remote-fs.target
+
+ [Service]
+ Type=simple
+ ExecStart=/opt/kafka/bin/zookeeper-server-start.sh /opt/kafka/config/zookeeper.properties
+ ExecStop=/opt/kafka/bin/zookeeper-server-stop.sh
+ Restart=on-abnormal
+
+ [Install]
+ WantedBy=multi-user.target
```
- # Notification Service Config
- #Port on which service runs
- APPLICATION_PORT = 3000
+ Save and exit.
- #Application environment
- APPLICATION_ENV = development
+4. Reload systemd:
- #Route after base url
- APPLICATION_BASE_URL = /notification/
+ ```bash
+ $ sudo systemctl daemon-reload
+ ```
- #Kafka endpoint
- KAFKA_HOST = "localhost:9092"
+5. Configure Kafka:
- #kafka topic name
- KAFKA_TOPIC ="testTopic"
+ ```bash
+ $ sudo nano /etc/systemd/system/kafka.service
+ ```
- #kafka consumer group id
- KAFKA_GROUP_ID = "notification"
+ Paste the following lines into the `kafka.service` file:
- #sendgrid api key
- SENDGRID_API_KEY = "SG.sdssd.dsdsd.XVSDGFEBGEB.sddsd"
+ ```ini
+ [Unit]
+ Description=Apache Kafka Service
+ Documentation=http://kafka.apache.org/documentation.html
+ Requires=zookeeper.service
- #sendgrid sender email address
- SENDGRID_FROM_MAIL = "test@gmail.com"
+ [Service]
+ Type=simple
+ Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"
+ ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties
+ ExecStop=/opt/kafka/bin/kafka-server-stop.sh
+ [Install]
+ WantedBy=multi-user.target
```
-4. Install Npm packages
+ Save and exit.
+
+6. Reload systemd:
+ ```bash
+ $ sudo systemctl daemon-reload
```
- ELEVATE/notification/src$ npm install
+
+7. Start Zookeeper:
+
+ ```bash
+ $ sudo systemctl start zookeeper
```
-5. Start Notification server
+ Check status:
+ ```bash
+ $ sudo systemctl status zookeeper
```
- ELEVATE/notification/src$ npm start
+
+ Zookeeper service status should be shown as active (running).
+
+8. Start Kafka:
+
+ ```bash
+ $ sudo systemctl start kafka
```
-
+ Check status:
-
+ ```bash
+ $ sudo systemctl status kafka
+ ```
+
+ Kafka status should be shown as active (running).
+
+### Install PM2
+
+Refer to [How To Set Up a Node.js Application for Production on Ubuntu 22.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-22-04).
+
+**Run the following command**
+
+```bash
+$ sudo npm install pm2@latest -g
+```
+
+## Setting up Repository
+
+### Clone the notification repository to /opt/backend directory
+
+```bash
+opt/backend$ git clone -b develop-2.5 --single-branch "https://github.com/ELEVATE-Project/notification.git"
+```
+
+### Install Npm packages from src directory
+
+```bash
+backend/notification/src$ sudo npm i
+```
+
+### Create .env file in src directory
+
+```bash
+notification/src$ sudo nano .env
+```
+
+Copy-paste the following env variables to the `.env` file:
+
+```env
+# Notification Service Config
+
+# Port on which service runs
+APPLICATION_PORT=3002
+
+# Application environment
+APPLICATION_ENV=development
-# Tech stack
+# Route after the base URL
+APPLICATION_BASE_URL=/notification/
-- Node - 16.0.0
-- Kafka - 3.1.0
-- Jest - 28.1.1
-- MongoDB - 4.1.4
+# Kafka endpoint
+KAFKA_HOST="localhost:9092"
+
+# Kafka topic name
+KAFKA_TOPIC="dev.notification"
+
+# Kafka consumer group id
+KAFKA_GROUP_ID="elevate-notification"
+
+# Sendgrid API key
+SENDGRID_API_KEY="SG.asd9f87a9s8d7f."
+
+# Sendgrid sender email address
+SENDGRID_FROM_MAIL="no-reply@some.org"
+
+# Api doc URL
+API_DOC_URL= "/notification/api-doc"
+
+INTERNAL_ACCESS_TOKEN="internal_access_token"
+ERROR_LOG_LEVEL='silly'
+DISABLE_LOG=false
+DEV_DATABASE_URL=postgres://shikshalokam:slpassword@localhost:9700/elevate_notification
+
+ZEST_ENV= "ZEST_ENV"
+created_time= "2023-12-29T17:04:19.017783534Z"
+custom_metadata= null
+destroyed=false
+version=8
+```
+
+Save and exit.
+
+## Setting up Databases
+
+**Log into the postgres user**
+
+```bash
+sudo su postgres
+```
+
+**Log into psql**
+
+```bash
+psql -p 9700
+```
+
+**Create a database user/role:**
+
+```sql
+CREATE USER shikshalokam WITH ENCRYPTED PASSWORD 'slpassword';
+```
+
+**Create the elevate_notification database**
+
+```sql
+CREATE DATABASE elevate_notification;
+GRANT ALL PRIVILEGES ON DATABASE elevate_notification TO shikshalokam;
+\c elevate_notification
+GRANT ALL ON SCHEMA public TO shikshalokam;
+```
+
+## Running Migrations To Create Tables
+
+**Exit the postgres user account and install sequelize-cli globally**
+
+```bash
+$ sudo npm i sequelize-cli -g
+```
+
+**Navigate to the src folder of notification service and run sequelize-cli migration command:**
+
+```bash
+notification/src$ npx sequelize-cli db:migrate
+```
+
+**Now all the tables must be available in the Citus databases**
+
+## Start the Service
+
+Navigate to the src folder of notification service and run pm2 start command:
+
+```bash
+notification/src$ pm2 start app.js -i 2 --name elevate-notification
+```
+
+#### Run pm2 ls command
+
+```bash
+$ pm2 ls
+```
+
+Output should look like this (Sample output, might slightly differ in your installation):
+
+```bash
+┌────┬─────────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
+│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
+├────┼─────────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
+│ 19 │ elevate-notification │ default │ 1.0.0 │ cluster │ 88026 │ 47h │ 0 │ online │ 0% │ 113.2mb │ jenkins │ disabled │
+│ 20 │ elevate-notification │ default │ 1.0.0 │ cluster │ 88036 │ 47h │ 0 │ online │ 0% │ 80.3mb │ jenkins │ disabled │
+└────┴─────────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
+```
+
+This concludes the service and dependency setup.
+
+
+
+
# Run tests
diff --git a/src/api-doc/notification-2.5-setup.md b/src/api-doc/notification-2.5-setup.md
new file mode 100644
index 00000000..8783eada
--- /dev/null
+++ b/src/api-doc/notification-2.5-setup.md
@@ -0,0 +1,286 @@
+# ShikshaLokam Notification Service Documentation
+
+## System Requirements
+
+- **Operating System:** Ubuntu 22
+- **Node.js:** v20
+- **PostgreSQL:** 16
+- **Citus:** 12.1
+- **Apache Kafka:** 3.5.0
+
+## Installations
+
+### Install Node.js LTS
+
+Refer to the [NodeSource distributions installation scripts](https://github.com/nodesource/distributions#installation-scripts) for Node.js installation.
+
+```bash
+$ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\
+sudo apt-get install -y nodejs
+```
+
+### Install Build Essential
+
+```bash
+$ sudo apt-get install build-essential
+```
+
+### Install Kafka
+
+Refer to [Kafka Ubuntu 22.04 setup guide](https://www.fosstechnix.com/install-apache-kafka-on-ubuntu-22-04-lts/)
+
+1. Install OpenJDK 11:
+
+ ```bash
+ $ sudo apt install openjdk-11-jdk
+ ```
+
+2. Download and extract Kafka:
+
+ ```bash
+ $ sudo wget https://downloads.apache.org/kafka/3.5.0/kafka_2.12-3.5.0.tgz
+ $ sudo tar xzf kafka_2.12-3.5.0.tgz
+ $ sudo mv kafka_2.12-3.5.0 /opt/kafka
+ ```
+
+3. Configure Zookeeper:
+
+ ```bash
+ $ sudo nano /etc/systemd/system/zookeeper.service
+ ```
+
+ Paste the following lines into the `zookeeper.service` file:
+
+ ```ini
+ /etc/systemd/system/zookeeper.service
+ [Unit]
+ Description=Apache Zookeeper service
+ Documentation=http://zookeeper.apache.org
+ Requires=network.target remote-fs.target
+ After=network.target remote-fs.target
+
+ [Service]
+ Type=simple
+ ExecStart=/opt/kafka/bin/zookeeper-server-start.sh /opt/kafka/config/zookeeper.properties
+ ExecStop=/opt/kafka/bin/zookeeper-server-stop.sh
+ Restart=on-abnormal
+
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+ Save and exit.
+
+4. Reload systemd:
+
+ ```bash
+ $ sudo systemctl daemon-reload
+ ```
+
+5. Configure Kafka:
+
+ ```bash
+ $ sudo nano /etc/systemd/system/kafka.service
+ ```
+
+ Paste the following lines into the `kafka.service` file:
+
+ ```ini
+ [Unit]
+ Description=Apache Kafka Service
+ Documentation=http://kafka.apache.org/documentation.html
+ Requires=zookeeper.service
+
+ [Service]
+ Type=simple
+ Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"
+ ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties
+ ExecStop=/opt/kafka/bin/kafka-server-stop.sh
+
+ [Install]
+ WantedBy=multi-user.target
+ ```
+
+ Save and exit.
+
+6. Reload systemd:
+
+ ```bash
+ $ sudo systemctl daemon-reload
+ ```
+
+7. Start Zookeeper:
+
+ ```bash
+ $ sudo systemctl start zookeeper
+ ```
+
+ Check status:
+
+ ```bash
+ $ sudo systemctl status zookeeper
+ ```
+
+ Zookeeper service status should be shown as active (running).
+
+8. Start Kafka:
+
+ ```bash
+ $ sudo systemctl start kafka
+ ```
+
+ Check status:
+
+ ```bash
+ $ sudo systemctl status kafka
+ ```
+
+ Kafka status should be shown as active (running).
+
+### Install PM2
+
+Refer to [How To Set Up a Node.js Application for Production on Ubuntu 22.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-22-04).
+
+**Run the following command**
+
+```bash
+$ sudo npm install pm2@latest -g
+```
+
+## Setting up Repository
+
+### Clone the notification repository to /opt/backend directory
+
+```bash
+opt/backend$ git clone -b develop-2.5 --single-branch "https://github.com/ELEVATE-Project/notification.git"
+```
+
+### Install Npm packages from src directory
+
+```bash
+backend/notification/src$ sudo npm i
+```
+
+### Create .env file in src directory
+
+```bash
+notification/src$ sudo nano .env
+```
+
+Copy-paste the following env variables to the `.env` file:
+
+```env
+# Notification Service Config
+
+# Port on which service runs
+APPLICATION_PORT=3002
+
+# Application environment
+APPLICATION_ENV=development
+
+# Route after the base URL
+APPLICATION_BASE_URL=/notification/
+
+# Kafka endpoint
+KAFKA_HOST="localhost:9092"
+
+# Kafka topic name
+KAFKA_TOPIC="dev.notification"
+
+# Kafka consumer group id
+KAFKA_GROUP_ID="elevate-notification"
+
+# Sendgrid API key
+SENDGRID_API_KEY="SG.asd9f87a9s8d7f."
+
+# Sendgrid sender email address
+SENDGRID_FROM_MAIL="no-reply@some.org"
+
+# Api doc URL
+API_DOC_URL= "/notification/api-doc"
+
+INTERNAL_ACCESS_TOKEN="internal_access_token"
+ERROR_LOG_LEVEL='silly'
+DISABLE_LOG=false
+DEV_DATABASE_URL=postgres://shikshalokam:slpassword@localhost:9700/elevate_notification
+
+ZEST_ENV= "ZEST_ENV"
+created_time= "2023-12-29T17:04:19.017783534Z"
+custom_metadata= null
+destroyed=false
+version=8
+```
+
+Save and exit.
+
+## Setting up Databases
+
+**Log into the postgres user**
+
+```bash
+sudo su postgres
+```
+
+**Log into psql**
+
+```bash
+psql -p 9700
+```
+
+**Create a database user/role:**
+
+```sql
+CREATE USER shikshalokam WITH ENCRYPTED PASSWORD 'slpassword';
+```
+
+**Create the elevate_notification database**
+
+```sql
+CREATE DATABASE elevate_notification;
+GRANT ALL PRIVILEGES ON DATABASE elevate_notification TO shikshalokam;
+\c elevate_notification
+GRANT ALL ON SCHEMA public TO shikshalokam;
+```
+
+## Running Migrations To Create Tables
+
+**Exit the postgres user account and install sequelize-cli globally**
+
+```bash
+$ sudo npm i sequelize-cli -g
+```
+
+**Navigate to the src folder of notification service and run sequelize-cli migration command:**
+
+```bash
+notification/src$ npx sequelize-cli db:migrate
+```
+
+**Now all the tables must be available in the Citus databases**
+
+## Start the Service
+
+Navigate to the src folder of notification service and run pm2 start command:
+
+```bash
+notification/src$ pm2 start app.js -i 2 --name elevate-notification
+```
+
+#### Run pm2 ls command
+
+```bash
+$ pm2 ls
+```
+
+Output should look like this (Sample output, might slightly differ in your installation):
+
+```bash
+┌────┬─────────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
+│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
+├────┼─────────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
+│ 19 │ elevate-notification │ default │ 1.0.0 │ cluster │ 88026 │ 47h │ 0 │ online │ 0% │ 113.2mb │ jenkins │ disabled │
+│ 20 │ elevate-notification │ default │ 1.0.0 │ cluster │ 88036 │ 47h │ 0 │ online │ 0% │ 80.3mb │ jenkins │ disabled │
+└────┴─────────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
+```
+
+This concludes the service and dependency setup.
diff --git a/src/middlewares/validator.js b/src/middlewares/validator.js
index c783ead3..1d8f456e 100644
--- a/src/middlewares/validator.js
+++ b/src/middlewares/validator.js
@@ -9,7 +9,10 @@ const fs = require('fs')
module.exports = (req, res, next) => {
try {
- require(`@validators/${req.params.version}/${req.params.controller}`)[req.params.method](req)
+ const version = (req.params.version.match(/^v\d+$/) || [])[0] // Match version like v1, v2, etc.
+ const controllerName = (req.params.controller.match(/^[a-zA-Z0-9_-]+$/) || [])[0] // Allow only alphanumeric characters, underscore, and hyphen
+ const method = (req.params.method.match(/^[a-zA-Z0-9]+$/) || [])[0] // Allow only alphanumeric characters
+ require(`@validators/${version}/${controllerName}`)[method](req)
} catch (error) {}
next()
}
diff --git a/src/routes/index.js b/src/routes/index.js
index fce316a5..13a3ece0 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -12,16 +12,94 @@ const expressValidator = require('express-validator')
const fs = require('fs')
const { elevateLog, correlationId } = require('elevate-logger')
const logger = elevateLog.init()
+const path = require('path')
module.exports = (app) => {
app.use(authenticator)
// app.use(pagination)
app.use(expressValidator())
+ async function getAllowedControllers(directoryPath) {
+ try {
+ const getAllFilesAndDirectories = (dir) => {
+ let filesAndDirectories = []
+ fs.readdirSync(dir).forEach((item) => {
+ const itemPath = path.join(dir, item)
+ const stat = fs.statSync(itemPath)
+ if (stat.isDirectory()) {
+ filesAndDirectories.push({
+ name: item,
+ type: 'directory',
+ path: itemPath,
+ })
+ filesAndDirectories = filesAndDirectories.concat(getAllFilesAndDirectories(itemPath))
+ } else {
+ filesAndDirectories.push({
+ name: item,
+ type: 'file',
+ path: itemPath,
+ })
+ }
+ })
+ return filesAndDirectories
+ }
+
+ const allFilesAndDirectories = getAllFilesAndDirectories(directoryPath)
+ const allowedControllers = allFilesAndDirectories
+ .filter((item) => item.type === 'file' && item.name.endsWith('.js'))
+ .map((item) => path.basename(item.name, '.js')) // Remove the ".js" extension
+ const allowedVersions = allFilesAndDirectories
+ .filter((item) => item.type === 'directory')
+ .map((item) => item.name)
+
+ return {
+ allowedControllers,
+ allowedVersions,
+ }
+ } catch (err) {
+ console.error('Unable to scan directory:', err)
+ return {
+ allowedControllers: [],
+ directories: [],
+ }
+ }
+ }
async function router(req, res, next) {
let controllerResponse
let validationError
+ const version = (req.params.version.match(/^v\d+$/) || [])[0] // Match version like v1, v2, etc.
+ const controllerName = (req.params.controller.match(/^[a-zA-Z0-9_-]+$/) || [])[0] // Allow only alphanumeric characters, underscore, and hyphen
+ const file = req.params.file ? (req.params.file.match(/^[a-zA-Z0-9_-]+$/) || [])[0] : null // Same validation as controller, or null if file is not provided
+ const method = (req.params.method.match(/^[a-zA-Z0-9]+$/) || [])[0] // Allow only alphanumeric characters
+ try {
+ if (!version || !controllerName || !method || (req.params.file && !file)) {
+ // Invalid input, return an error response
+ const error = new Error('Invalid Path')
+ error.statusCode = 400
+ throw error
+ }
+
+ const directoryPath = path.resolve(__dirname, '..', 'controllers')
+
+ const { allowedControllers, allowedVersions } = await getAllowedControllers(directoryPath)
+
+ // Validate version
+ if (!allowedVersions.includes(version)) {
+ const error = new Error('Invalid version.')
+ error.statusCode = 400
+ throw error
+ }
+ // Validate controller
+ if (!allowedControllers.includes(controllerName)) {
+ const error = new Error('Invalid controller.')
+ error.statusCode = 400
+ throw error
+ }
+ } catch (error) {
+ return next(error)
+ }
+
/* Check for input validation error */
try {
validationError = req.validationErrors()
@@ -46,16 +124,14 @@ module.exports = (app) => {
'@controllers/' + req.params.version + '/' + req.params.controller + '/' + req.params.file + '.js'
)
if (folderExists) {
- controller = require(`@controllers/${req.params.version}/${req.params.controller}/${req.params.file}`)
+ controller = require(`@controllers/${version}/${controllerName}/${file}`)
} else {
- controller = require(`@controllers/${req.params.version}/${req.params.controller}`)
+ controller = require(`@controllers/${version}/${controllerName}`)
}
} else {
- controller = require(`@controllers/${req.params.version}/${req.params.controller}`)
+ controller = require(`@controllers/${version}/${controllerName}`)
}
- controllerResponse = new controller()[req.params.method]
- ? await new controller()[req.params.method](req)
- : next()
+ controllerResponse = new controller()[method] ? await new controller()[method](req) : next()
} catch (error) {
// If controller or service throws some random error
return next(error)
@@ -94,7 +170,7 @@ module.exports = (app) => {
// Global error handling middleware, should be present in last in the stack of a middleware's
app.use((error, req, res, next) => {
- if (error.statusCode || error.responseCode || error.message) {
+ if (error.statusCode || error.responseCode) {
// Detailed error response
const status = error.statusCode || 500
const responseCode = error.responseCode || 'SERVER_ERROR'