Multi-tenancy with mongoose and express - node.js

I am working on a MERN SaaS application and I have read all manner of documents on multi-tenancy, where the goal is to create a layer of data isolation above the level of the user. putting all the info together I came about this solution which suggests making use of "continous-local-storage" However, after implementing it, I can't really get it to work, all I have seems logically right, I really can't figure out the issue. After implementation, my application refuses to load data from the database...
//lib/storage.js
const createNamespace = require('continuation-local-storage').createNamespace;
const namespaceName = ('request');
const ns = createNamespace(namespaceName);
const bindCurrentNamespace=function(req, res, next){
ns.bindEmitter(req);
ns.bindEmitter(res);
ns.run(() => {
next();
});
}
const setCurrentTenantId=function(tenantId){
return ns.set('tenantId', tenantId);
}
const getCurrentTenantId=function(){
return ns.get('tenantId');
}
module.exports ={
bindCurrentNamespace:function(){},
setCurrentTenantId:function(){},
getCurrentTenantId:function(){}
}
Sever.js
**********
// some code above
const storage= require('./lib/storage')
// multitenant logic
const BindCurrentNamespace = storage.bindCurrentNamespace
app.use(BindCurrentNamespace);
app.use((req, res, next) => {
// Get current user from session or token
const user = req.user
// Get current tenant from user here
// Make sure its a string
const tenantId = user.organization._id.toString()
setCurrentTenantId(tenantId);
next();
});
/lib/multiTenant.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const storage = require('./storage');
const GetCurrentTenantId = storage.getCurrentTenantId
const tenantModel = function (name, schema, options) {
return (props = {}) => {
schema.add({ tenantId: String });
const Model = mongoose.model(name, schema, options);
const { skipTenant } = props;
if (skipTenant) return Model;
Model.schema.set('discriminatorKey', 'tenantId');
const tenantId = GetCurrentTenantId;
const discriminatorName = `${Model.modelName}-${tenantId}`;
const existingDiscriminator = (Model.discriminators || {})[discriminatorName];
return existingDiscriminator || Model.discriminator(discriminatorName, new Schema({}));
};
}
const tenantlessModel = function (name, schema, options) {
return () => mongoose.model(name, schema, options);
}
module.exports={
tenantModel:function(){},
tenantlessModel:function(){}
}
Modified Schema
// Employee schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const Tenant= require('../lib/multiTenant')
const TenantModel=Tenant.tenantModel;
//create Schema
const EmployeeSchema = new mongoose.Schema({
name: String,
department: String,
origin: String,
wages:Number,
overtime:{type:Number,
default:0},
joinDate: String,
attendances: Object
},{timestamps:true});
const Employee=TenantModel("Employee", EmployeeSchema,'employee001');
module.exports=Employee
Usage
// #desc Get all Employee
//#routes Get/api/v1/employee
//#acess Public
exports.getAllEmployee = asyncHandler(async (req, res, next) => {
Employee().find({}, null, {sort: {name: 1}}, function(err, employees){
if(err){
res.status(500);
res.send(err);
} else {
res.json(employees);
}
});
});

Related

how to fetch subcollection's data in firestore?

I have built a webapp using node express to backend and for frontend I used Reactjs .
In firestore database has a "users" collection in there have many documents for every users. For each document have fields and have subcollections.
1st view
2nd view (in a subcollection view)
this is the sample database like my real database structure .I want fetch all users(inside the users table documents) andalso with subcollections fields.
For every users have same subcollections.(Like as this image user have 4 subcollections andaslo another user also have that same subcollections.)
For this I write the code like this.
model class
class Users {
constructor(id,name,email,provider,firstWord,leda,age,birthday,district,gender,familyChildren,fatherEducation,monthlyIncome,motherEducation,whichChild,awaSE,awaUN,kathakaraaSE,kathakaraaSE,kathakaraaUN) {
this.id = id;
this.name = name;
this.email = email;
this.provider = provider;
this.email = firstWord;
this.email = leda;
this.age = age;
this.birthday = birthday;
this.district = district;
this.gender = gender;
this.familyChildren = familyChildren;
this.fatherEducation = fatherEducation;
this.monthlyIncome = monthlyIncome;
this.motherEducation = motherEducation;
this.whichChild = whichChild;
this.awaSE = awaSE;
this.awaUN = awaUN;
this.kathakaraaSE = kathakaraaSE;
this.kathakaraaUN = kathakaraaUN;
}
}
module.exports = Users;
controller
'use strict';
const firebase = require('../db');
const Users = require('../models/users');
const firestore = firebase.firestore();
const getAllUsers = async (req, res, next) => {
try {
const users = await firestore.collection('users');
const data = await users.get();
const userArray = [];
if(data.empty) {
res.status(404).send('No user found');
}else {
data.forEach(doc => {
const users = new Users(
doc.id,
doc.data().name,
doc.data().email,
doc.data().provider,
doc.data().firstWord,
doc.data().leda,
doc.data().age,
doc.data().birthday,
doc.data().district,
doc.data().gender,
doc.data().familyChildren,
doc.data().fatherEducation,
doc.data().monthlyIncome,
doc.data().motherEducation,
doc.data().whichChild,
doc.data().awaSE,
doc.data().awaUN,
doc.data().kathakaraaSE,
doc.data().kathakaraaUN,
);
userArray.push(users);
});
res.send(userArray);
}
} catch (error) {
res.status(400).send(error.message);
}
}
module.exports = {
getAllUsers,
}
router class
const router = require("express").Router();
const { getAllUsers } = require('../controllers/userscontroller.js')
router.get('/AllUsers', getAllUsers);
module.exports = router;
model class image
1.users collection fields
2.childGrow collection fields
3.childPrivateDetails collection fields
4.familyDetails collection fields
5.wenath collection fields
but out put is
in there not display other collections fields.
How I do that using node express?
You can do something like this:
const getAllUsers = async (req, res, next) => {
try {
const users = await getFirestore().collection('users');
const data = await users.get();
const userArray = [];
if (data.empty) {
res.status(404).send('No user found');
} else {
for (let doc of data.docs) {
let firstSubCollectionData = await getFirestore().collection('users').doc(doc.id).collection('firstSubCollection').get();
let secondSubCollectionData = await getFirestore().collection('users').doc(doc.id).collection('secondSubCollectionData').get();
let thirdSubCollectionData = await getFirestore().collection('users').doc(doc.id).collection('thirdSubCollectionData').get();
let forthSubCollectionData = await getFirestore().collection('users').doc(doc.id).collection('forthSubCollectionData').get();
// construct your object here
// add it to list
}
res.send(userArray);
}
} catch (error) {
res.status(400).send(error.message);
}
}
BUT
sub-collections can be potentially a list of values. If not I think you have to redesign your data model.

How to use mongoose and nodejs to insert document without having a schema

I'm using nodejs and mongoose in my project, when I'm trying to add a document to mongodb database, Ifound an emty document, I dont know why ?
Method :
const createUser = async (req, res) => {
try {
req = matchedData(req)
const doesUserExists = await userExists(req.email)
if (!doesUserExists) {
res.status(201).json(await createItem(req,User))
}
} catch (error) {
handleError(res, error)
}
}
createItem :
const createItem = (req = {}, model = {}) => {
return new Promise((resolve, reject) => {
console.log(req)
model.create(req, (err, item) => {
if (err) {
reject(buildErrObject(422, err.message))
}
resolve(item)
})
})
}
User Model :
const mongoose = require('mongoose')
const mongoosePaginate = require('mongoose-paginate-v2')
const UserSchema = new mongoose.Schema({})
UserSchema.plugin(mongoosePaginate)
module.exports = mongoose.model('User', UserSchema, 'users')
postman test :
can anyone understand why I'm getting en empty result please ?
this could help -> https://mongoosejs.com/docs/guide.html#strict
im guessing you should have it like
const UserSchema = new mongoose.Schema({..}, { strict: false })

NodeJs - mongoose function always return null

I am new to nodeJs and i am trying to return apikey from the function saveApi but it always returns null. Can you help me with that?
var saveApi = async (email) => {
const apikeyvalue = apigen.create().apiKey;
console.log("key",apikeyvalue)
const apikey = require("../models/api");
var finalkey=null
try {
let key = new apikey({ email: email, apikey: apikeyvalue })
finalkey = await key.save();
} catch (e) {
throw new ProductCreateError();
}
return finalkey.apikey
};
Here is the definition of my apikey model
var mongoose=require('mongoose');
var apiSchema= new mongoose.Schema({
email:String,
apikey:String
});
module.exports = mongoose.model(
'apikey', apiSchema, 'apikeys');
and here is the function call to saveApi()
var api = saveApi.then(obj.username)

Use collection get from req.params - node.js

I'm using node.js and mongodb I need to know how to request the reading of a certain collection via API
(day_07-07-2021 is the collection to search)
in the Node.js route I wanted to query the collection day07072021, I wanted to know how I change the collection (on the fly) to query the route using the "req.param" received via URL.
so after setting the collection I'll do find();
Each day will have a collection.
http://localhost:3000/registry/windows2021/1101/day07072021
My actual code is (updated after: Selva Mary), but still not working.
logs.js
const express = require('express')
const router = express.Router()
const logs = require('../models/registry')
router.get('/:device/:rule/:bydate', async (req, res) => {
try {
let byDate = req.params.bydate +"info"
const logues = await db.collection(byDate).find({
'agent.name': req.params.device,
'rule.id': req.params.rule
}).sort({ 'id': -1 }).limit(15);
res.json(logues)
}
catch (err) {
res.status(500).json({ message: err.message })
}
})
server.js
require('dotenv').config()
const express = require('express')
const app = express()
const mongoose = require('mongoose')
const log = mongoose.set('debug', true)
mongoose.connect('mongodb://db:27017/datainfo', { useNewUrlParser: true,useUnifiedTopology: true })
const db = mongoose.connection
const registryRouter = require('./routes/registry')
app.use('/registry', registryRouter)
I hope its clear.
I get the message:
{"message":"db is not defined"}
Try this
router.get('/:device/:rule/:bydate', async (req, res) => {
try {
let byDate = req.params.bydate +"" // this makes the bydate as string. Collection name need to be in string
const logues = await logs.collection(byDate).find({
'agent.name': req.params.device,
'rule.id': req.params.rule
}).sort({ 'id': -1 }).limit(15);
res.json(logues)
}
catch (err) {
res.status(500).json({ message: err.message })
}
})

error:"UnhandledPromiseRejectionWarning: Error: Invalid message options" in nodejs

I am trying to call registerValidation function that is defined in file validation.js from auth.js, but error appears that
UnhandledPromiseRejectionWarning: Error: Invalid message options
Can anyone tell me why and how to correct it?
auth.js
const router = require("express").Router();
const User = require("../model/User")
const {registerValidation} = require("./validation")
router.post('/register', async(req, res)=>{
//Lets validate the data
//error appears here
const {error} = registerValidation(req.body);
if(error){
return res.status(400).send(error.details[0].message)
}
const user = new User({
name:req.body.name,
email:req.body.email,
password:req.body.password
});
try{
const savedUser = await user.save();
res.send(savedUser);
}catch(err){
res.status(400).send(err)
}
})
module.exports = router;
validation.js
//VALIDATION
const Joi = require("#hapi/joi")
const registerValidation = (data) => {
const schema = Joi.object({
name:Joi.string().min(6).required(),
email:Joi.string().min(6).required().email(),
password:Joi.string().min(6).required()
});
return schema.validate(data, schema)
}
const loginValidation = data =>{
const schema = Joi.object({
email:Joi.string().min(6).required().email(),
password:Joi.string().min(6).required()
});
return schema.validate(data, schema)
}
module.exports = {registerValidation, loginValidation};
You need to wrap your functions inside an object and then export that object, like in the following example:
const registerValidation = (data) => {...}
const loginValidation = (data) => {...}
module.exports = {
registerValidation,
loginValidation
}
Alternatively, since module.exports is an object itself, you could do something like the following:
const registerValidation = (data) => {...}
const loginValidation = (data) => {...}
module.exports.registerValidation = registerValidation;
module.exports.loginValidation = loginValidation;
the registerValidation and loginValidation function should be like given below
const Joi = require("#hapi/joi");
const registerValidation = (data) => {
console.log("body ", data)
const schema = Joi.object({
name:Joi.string().min(6).required(),
email:Joi.string().min(6).required().email(),
password:Joi.string().min(6).required()
});
return schema.validate(data) //change here
}
const loginValidation = data =>{
const schema = Joi.object({
email:Joi.string().min(6).required().email(),
password:Joi.string().min(6).required()
});
return schema.validate(data) //change here
}
module.exports = {registerValidation, loginValidation};

Resources