Delete Server directory

This commit is contained in:
Mohammad Khalilzadeh authored and GitHub committed 2024-05-18 09:04:41 +03:30
1 parent b2c8e50043
commit 3a94abb649
17 files changed
-3147

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
node_modules
.env
*.log
-15
View File
@@ -1,15 +0,0 @@
const PORT = process.env.PORT || 5000;
const connectDbAndRunServer = async (app, db) => {
try {
await db.connect();
app.listen(PORT, () => {
console.log(`server started on port:${PORT}`);
});
} catch (error) {
console.log("Failed to connect to DB");
console.error(error);
}
};
module.exports = { connectDbAndRunServer };
-50
View File
@@ -1,50 +0,0 @@
const express = require("express");
const logger = require("../utils/logger");
/**
* @function
* @param {express.Request} req
* @param {express.Response} res
* @returns {{success: Boolean, msg: String}}
*/
const registerController = async (req, res) => {
// joi validation
try {
await registerValidation.validateAsync(req.body);
} catch (error) {
return res
.status(400)
.json({ success: false, msg: error.details[0].message });
}
// Check if the user exists
try {
const isUser = await req.db.getUserByEmail(req, res);
if (isUser) {
logger.warning("User already exists!", {
service: "auth",
userId: isUser._id,
});
return res
.status(400)
.json({ success: false, msg: "User already exists!" });
}
} catch (error) {
logger.error(error.message, { service: "auth" });
return res.status(500).json({ success: false, msg: error.message });
}
try {
// Create a new user
const newUser = await req.db.insertUser(req, res);
// TODO: Send an email to user
// Will add this later
logger.info("New user created!", { service: "auth", userId: newUser._id });
return res.json({ success: true, msg: "User created}", data: newUser });
} catch (error) {
logger.error(error.message, { service: "auth" });
return res.status(500).json({ success: false, msg: error.message });
}
};
module.exports = { registerController };
-64
View File
@@ -1,64 +0,0 @@
const {
getMonitorsByIdValidation,
getMonitorsByUserIdValidation,
} = require("../validation/joi");
const logger = require("../utils/logger");
// Gets all monitors
const getAllMonitors = async (req, res) => {
try {
const monitors = await req.db.getAllMonitors();
return res.json({ success: true, msg: "Monitors found", data: monitors });
} catch (error) {
logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
// Get a monitor by ID
const getMonitorById = async (req, res) => {
const { error } = getMonitorsByIdValidation.validate(req.params);
if (error) {
return res
.status(400)
.json({ success: false, msg: error.details[0].message });
}
try {
const monitorId = req.params.monitorId;
const monitor = await req.db.getMonitorById(monitorId);
return res.json({ success: true, msg: "Monitor found", data: monitor });
} catch (error) {
logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
// Gets a monitor by user ID
const getMonitorsByUserId = async (req, res) => {
const { error } = getMonitorsByUserIdValidation.validate(req.params);
if (error) {
return res
.status(400)
.json({ success: false, msg: error.details[0].message });
}
try {
const userId = req.params.userId;
const monitors = await req.db.getMonitorsByUserId(userId);
logger.info(`Monitors for user ${userId} found`, {
service: "monitor",
userId: userId,
});
return res.json({
success: true,
msg: `Monitors for user ${userId} found`,
data: monitors,
});
} catch (error) {
logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId };
-110
View File
@@ -1,110 +0,0 @@
// **************************
// The idea here is to provide a layer of abstraction between the database and whoever is using it.
// Instead of directly calling mongoose methods, we can call the methods on the DB object.
// If this were Typescript or Java or Golang an interface would be implemented to ensure the methods are available.
// But we do the best we can with Javascript.
//
// If the methods are consistent all we have to do to swap out one DB for another is simply change the import.
//
// Example:
// We start with the fake DB:
//
// const db = require("../db/FakeDb");
// const monitors = await db.getAllMonitors();
//
// And when we want to swtich to a real DB, all we have to do is swap the import
//
// const db = require("../db/MongoDb");
// const monitors = await db.getAllMonitors();
//
// The rest of the code is the same, as all the `db` methods are standardized.
// **************************
const Monitor = require("../models/Monitor");
const UserModel = require("../models/user");
const FAKE_MONITOR_DATA = [];
const USERS = [];
for (let i = 0; i < 1; i++) {
FAKE_MONITOR_DATA.push(
new Monitor({
userId: i % 2 === 0 ? 1 : 2,
name: `Monitor ${i}`,
description: `Description for Monitor ${i}`,
url: `https://monitor${i}.com`,
isActive: true,
interval: Math.floor(Math.random() * 10) + 1,
updated_at: new Date(),
created_at: new Date(),
})
);
}
const connect = async () => {
try {
await console.log("Connected to FakeDB");
} catch (error) {
console.error(error);
}
};
const insertUser = async (req, res) => {
try {
const newUser = new UserModel({ ...req.body });
USERS.push(newUser);
return newUser;
} catch (error) {
throw error;
}
};
const getUserByEmail = async (req, res) => {
const email = req.body.email;
try {
const idx = USERS.findIndex((user) => {
return user.email === email;
});
if (idx === -1) {
return null;
}
return USERS[idx];
} catch (error) {
throw new Error(`User with email ${email} not found`);
}
};
const getAllMonitors = async () => {
return FAKE_MONITOR_DATA;
};
const getMonitorById = async (monitorId) => {
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
return monitor.id === monitorId;
});
if (idx === -1) {
throw new Error(`Monitor with id ${monitorId} not found`);
}
return FAKE_MONITOR_DATA[idx];
};
const getMonitorsByUserId = async (userId) => {
const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => {
return monitor.userId === userId;
});
if (userMonitors.length === 0) {
throw new Error(`Monitors for user ${userId} not found`);
}
return userMonitors;
};
module.exports = {
connect,
insertUser,
getUserByEmail,
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
};
-71
View File
@@ -1,71 +0,0 @@
const Monitor = require("../models/Monitor");
const mongoose = require("mongoose");
const UserModel = require("../models/user");
const connect = async () => {
try {
await mongoose.connect(process.env.DB_CONNECTION_STRING);
console.log("Connected to MongoDB");
} catch (error) {
console.error("Failed to connect to MongoDB");
throw error;
}
};
const insertUser = async (req, res) => {
try {
const newUser = await UserModel.create({ ...req.body }).select('-password');
return newUser;
} catch (error) {
throw error;
}
};
const getUserByEmail = async (req, res) => {
try {
// Returns null if no user is found
const user = await UserModel.findOne({ email: req.body.email }).select('-password');
return user;
} catch (error) {
throw error;
}
};
// Gets all monitors
const getAllMonitors = async (req, res) => {
try {
const monitors = await Monitor.find();
return monitors;
} catch (error) {
throw error;
}
};
// Get a monitor by ID
const getMonitorById = async (req, res) => {
try {
const monitor = await Monitor.findById(req.params.monitorId);
return monitor;
} catch (error) {
throw error;
}
};
// Gets a monitor by user ID
const getMonitorsByUserId = async (req, res) => {
try {
const monitors = await Monitor.find({ userId: req.params.userId });
return monitors;
} catch (error) {
throw error;
}
};
module.exports = {
connect,
insertUser,
getUserByEmail,
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
};
-91
View File
@@ -1,91 +0,0 @@
const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const authRouter = require("./routes/authRoute");
const monitorRouter = require("./routes/monitorRoute");
const { connectDbAndRunServer } = require("./configs/db");
require("dotenv").config();
const logger = require("./utils/logger");
// const { sendEmail } = require('./utils/sendEmail')
const pingSerivce = require("./service/pingService");
/**
* DB Notes
* Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
* Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
* We can easily swap between any type of data source as long as the methods are implemented
*
* FakeDB
* const db = require("./db/FakeDb");
*
* MongoDB
* const db = require("./db/MongoDB");
*/
const DB_TYPE = {
MongoDB: () => require("./db/MongoDB"),
FakedDB: () => require("./db/FakeDb"),
};
const db = DB_TYPE[process.env.DB_TYPE]
? DB_TYPE[process.env.DB_TYPE]()
: require("./db/FakeDb");
/**
* NOTES
* Email Service will be added
* Logger Service will be added (Winston or similar)
*/
const app = express();
// middlewares
app.use(
cors()
//We will add configuration later
);
app.use(express.json());
app.use(helmet());
// **************************
// Make DB accessible anywhere we have a Request object
// By adding the DB to the request object, we can access it in any route
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
// **************************
app.use((req, res, next) => {
req.db = db;
next();
});
//routes
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/monitors", monitorRouter);
// Testing email service
// app.use('/sendEmail', async (req, res) => {
// const response = sendEmail(['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '<h1>Testing Bluewavelabs</h1>');
// console.log(response);
// })
//health check
app.use("/api/v1/healthy", (req, res) => {
try {
logger.info("Checking Health of the server.");
return res.status(200).json({ message: "Healthy" });
} catch (error) {
logger.error(error.message);
return res.status(500).json({ message: error.message });
}
});
connectDbAndRunServer(app, db);
pingSerivce.startPingService(db);
// Cleanup
const cleanup = async () => {
await pingSerivce.cleanup();
process.exit(0);
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
-37
View File
@@ -1,37 +0,0 @@
const mongoose = require("mongoose");
const MonitorSchema = mongoose.Schema({
userId: {
type: String,
},
name: {
type: String,
required: true,
},
description: {
type: String,
},
url: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: true,
},
interval: {
// in milliseconds
type: Number,
default: 60000,
},
updated_at: {
type: Date,
default: Date.now,
},
created_at: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Monitor", MonitorSchema);
-51
View File
@@ -1,51 +0,0 @@
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const UserSchema = mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
profilePicUrl: {
type: String,
},
isActive: {
type: Boolean,
default: true,
},
isVerified: {
type: Boolean,
default: false,
},
updated_at: {
type: Date,
default: Date.now,
},
created_at: {
type: Date,
default: Date.now,
},
});
UserSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
this.password = bcrypt.hash(this.password, salt);
});
module.exports = mongoose.model("User", UserSchema);
-2394
View File
File diff suppressed because it is too large. Load diff
-28
View File
@@ -1,28 +0,0 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"bullmq": "5.7.8",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"helmet": "^7.1.0",
"joi": "^17.13.1",
"mailersend": "^2.2.0",
"mongoose": "^8.3.3",
"winston": "^3.13.0"
},
"devDependencies": {
"nodemon": "3.1.0"
}
}
-6
View File
@@ -1,6 +0,0 @@
const router = require('express').Router();
const { registerController } = require('../controllers/authController')
router.post('/register', registerController)
module.exports = router;
-7
View File
@@ -1,7 +0,0 @@
const router = require("express").Router();
const monitorController = require("../controllers/monitorController");
router.get("/", monitorController.getAllMonitors);
router.get("/:monitorId", monitorController.getMonitorById);
router.get("/user/:userId", monitorController.getMonitorsByUserId);
module.exports = router;
-123
View File
@@ -1,123 +0,0 @@
const { Queue, Worker } = require("bullmq");
const logger = require("../utils/logger");
require("dotenv").config();
/**
* NOTES
* This code will be resource heavy as we will be creating
* many workers and jobs. Operations should be robust and have good
* error handling and retry functionality.
* There will be a lot of I/O operations as well, so we should be
* aware of asynchronous operations and handle them properly.
*/
const connection = {
host: process.env.BULL_MQ_HOST || "127.0.0.1",
port: process.env.BULL_MQ_PORT || 6379,
};
const JOBS_PER_WORKER = 5;
const MAX_CREATE_WORKER_RETRIES = 3;
const SERVICE_NAME = "pingService";
const QUEUE_NAME = "monitors";
let queue;
let workers = [];
const enqueueJob = async (queue, monitor) => {
try {
await queue.add(
QUEUE_NAME,
{ monitor },
{ delay: monitor.interval * 1000 }
);
logger.info("Job enqueued", { service: SERVICE_NAME });
} catch (error) {
logger.error(error.message, { service: SERVICE_NAME });
}
};
const pingMonitor = (monitor) => {
// TODO Implement pinging the monitor
console.log(`{url: ${monitor.url},`, `interval: ${monitor.interval}}`);
};
const createWorker = (queue, retries = 0) => {
try {
const worker = new Worker(
QUEUE_NAME,
async (job) => {
//Ping the monitor and enqueue the job again to constantly monitor
pingMonitor(job.data.monitor);
enqueueJob(queue, job.data.monitor);
},
{
connection,
}
);
logger.info("Worker created", { service: SERVICE_NAME });
return worker;
} catch (error) {
logger.error(error.message, { service: SERVICE_NAME });
// If creating a worker fails, recursively retry MAX_CREATE_WORKER_RETRIES times
if (retries < MAX_CREATE_WORKER_RETRIES) {
logger.info(
`Retrying to create worker (${
retries + 1
}/${MAX_CREATE_WORKER_RETRIES})`,
{ service: SERVICE_NAME }
);
return createWorker(queue, retries + 1);
}
return null;
}
};
const startPingService = async (db) => {
queue = new Queue(QUEUE_NAME, {
connection,
});
const monitors = await db.getAllMonitors();
for (let i = 0; i < monitors.length; i++) {
if (i % JOBS_PER_WORKER === 0) {
const worker = createWorker(queue);
worker && workers.push(worker);
}
enqueueJob(queue, monitors[i]);
}
// Make sure a worker was created, none will be created if jobs < JOBS_PER_WORKER
if (workers.length === 0) {
const worker = createWorker(queue);
worker && workers.push(worker);
if (!worker) {
logger.error("Failed to create workers", { service: SERVICE_NAME });
}
}
};
const cleanup = async () => {
try {
logger.info("Cleaning up Ping Service...", { service: SERVICE_NAME });
// Clean out the queue
const jobs = await queue.getJobs(["active", "waiting", "delayed"]);
// There could be a great many jobs, handle this asynchronously with Promise.all
await Promise.all(jobs.map((job) => job.remove()));
// Close workers. Same as jobs, there could be many workers we don't want to wait for each one to close.
await Promise.all(workers.map((worker) => worker.close()));
// Close the queue
await queue.close();
logger.info("Ping Service cleaned up", { service: SERVICE_NAME });
} catch (error) {
logger.error(error.message, { service: SERVICE_NAME });
}
};
module.exports = { startPingService, cleanup };
-26
View File
@@ -1,26 +0,0 @@
const winston = require('winston');
/**
* @module
* @example
* logger.info("Registered a new user!")
* logger.warn("User not found!")
* logger.error("Cannot save")
* @example
* "Specify service and ID in the log if applicable."
* logger.error("Descriptive Message",{"service":"monitor","monitorId":"123456"})
* logger.error("Incorrect Credentials",{"service":"Auth","userId":"654321"})
* logger.error("User not found!",{"service":"Auth"})
*/
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
],
});
module.exports = logger;
-43
View File
@@ -1,43 +0,0 @@
const { MailerSend, EmailParams, Sender, Recipient } = require('mailersend')
const logger = require('../utils/logger')
const mailersend = new MailerSend({
apiKey: process.env.MAILERSEND_API_KEY,
})
/**
* @function
* @param {[string]} receivers - takes an array of strings
* @param {string} subject - takes a single string
* @param {string} contentHTML - takes a single string that contains HTML
* @returns {JSON}
* @example
* sendEmail(['veysel@bluewavelabs.ca','alex@bluewavelabs.ca','monzer@bluewavelabs.ca'],'Testing Email Servide','<h1>BlueWaveLabs</h1>')
*/
// TODO: from email should be in .env file
const sendEmail = async (receivers,subject,contentHTML) => {
// Sender
const from = process.env.SYSTEM_EMAIL_ADDRESS;
const sender = new Sender(from);
// receivers
let recipients = []
receivers.map(email => recipients.push(new Recipient(email)));
// Set params
const emailParams = new EmailParams()
.setFrom(sender)
.setTo(recipients)
.setSubject(subject)
.setHtml(contentHTML);
try {
const response = await mailersend.email.send(emailParams);
logger.info("Email sent to receivers!",{"service":"Email"})
return response;
} catch (error) {
logger.error(error.body,{"service":"email"})
console.log(error.body)
}
}
module.exports = {sendEmail}
-28
View File
@@ -1,28 +0,0 @@
const joi = require("joi");
const user = require("../models/user");
const loginValidation = joi.object({
email: joi.string().email().required(),
password: joi.string().min(8).required(),
});
const registerValidation = joi.object({
firstname: joi.string().required(),
lastname: joi.string().required(),
email: joi.string().email().required(),
password: joi.string().min(8).required(),
});
const getMonitorsByIdValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorsByUserIdValidation = joi.object({
userId: joi.string().required(),
});
module.exports = {
loginValidation,
registerValidation,
getMonitorsByIdValidation,
getMonitorsByUserIdValidation,
};