Skip to content

Commit

Permalink
added connection manager and id helper functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ebrahim354 authored and JamaicanFriedChicken committed Sep 6, 2022
1 parent 08acd8c commit 80318d3
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 0 deletions.
19 changes: 19 additions & 0 deletions lib/ConnectionManager/addTenantConnection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const Tenant = require('../models/Tenant');
const Database = require('../Database/index');
const { setConnection, getConnection } = require('./connections');

module.exports = async (organizationId) => {
try {
const alreadyConnected = getConnection(organizationId);
if (alreadyConnected) return alreadyConnected;
const [tenant] = await Tenant.find({ organization: organizationId });
if (!tenant || !tenant.url) throw new Error('Organization not found!');
console.log('tenant', tenant);
let connection = new Database(tenant.url);
await connection.connect();
setConnection(tenant.organization, connection);
return connection;
} catch (e) {
console.log('organization not found!');
}
};
19 changes: 19 additions & 0 deletions lib/ConnectionManager/connections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const connections = {};

const setConnection = (orgId, connection) => {
connections[`${orgId}`] = connection;
};

const getConnection = (orgId) => {
if (!connections[orgId]) return null;
return connections[orgId];
};

const destroy = async () => {
for (let conn in connections) {
await connections[conn].disconnect();
delete connections[conn];
}
};

module.exports = { setConnection, getConnection, destroy };
9 changes: 9 additions & 0 deletions lib/ConnectionManager/destroyConnections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const connections = require('./connections');

module.exports = async () => {
try {
await connections.destroy();
} catch (e) {
console.log(e);
}
};
9 changes: 9 additions & 0 deletions lib/ConnectionManager/getTenantConnection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { getConnection } = require('./connections');

module.exports = (organizationId) => {
try {
return getConnection(organizationId);
} catch (e) {
console.log('organization not found!');
}
};
11 changes: 11 additions & 0 deletions lib/ConnectionManager/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const addTenantConnection = require('./addTenantConnection');
const getTenantConnection = require('./getTenantConnection');
const initTenants = require('./initTenants');
const destroyConnections = require('./destroyConnections');

module.exports = {
addTenantConnection,
getTenantConnection,
initTenants,
destroyConnections,
};
16 changes: 16 additions & 0 deletions lib/ConnectionManager/initTenants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const Tenant = require('../models/Tenant');
const Database = require('../Database/index');
const { setConnection } = require('./connections');

module.exports = async () => {
try {
const databases = await Tenant.find();
for (let db of databases) {
let connection = new Database(db.url);
await connection.connect();
setConnection(db.organization, connection);
}
} catch (e) {
console.log('connection failed');
}
};
3 changes: 3 additions & 0 deletions lib/helper_functions/addTenantId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = (tenantId, id) => {
return tenantId + ' ' + id;
};
4 changes: 4 additions & 0 deletions lib/helper_functions/getTenantFromId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = (fullId) => {
const [tenantId, id] = fullId.split(' ');
return { tenantId, id };
};
31 changes: 31 additions & 0 deletions lib/models/Tenant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const tenantSchema = new Schema({
organization: {
type: Schema.Types.ObjectId,
ref: 'Organizaiton',
},
url: {
type: String,
required: true,
},
type: {
type: String,
enum: ['MONGO', 'POSTGRES'],
default: 'MONGO',
required: true,
},
status: {
type: String,
required: true,
default: 'ACTIVE',
enum: ['ACTIVE', 'BLOCKED', 'DELETED'],
},
createdAt: {
type: Date,
default: Date.now,
},
});

module.exports = mongoose.model('Tenant', tenantSchema);
157 changes: 157 additions & 0 deletions tests/database/multi-tenancy.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const shortid = require('shortid');
const Tenant = require('../../lib/models/Tenant');
const connectionManager = require('../../lib/ConnectionManager');

const database = require('../../db');
const getUserIdFromSignUp = require('../functions/getUserIdFromSignup');
const Organization = require('../../lib/models/Organization');
const User = require('../../lib/models/User');
// const Post = require('../../lib/models/Post');
const tenantUrl =
'mongodb://localhost:27017/org1-tenant?retryWrites=true&w=majority';
const secondTenantUrl =
'mongodb://localhost:27017/org2-tenant?retryWrites=true&w=majority';

let adminId;
let organizationId;
let secondOrganizationId;

beforeAll(async () => {
// setting up 1 org, one user with 1 tenant record (on the main database).
require('dotenv').config();
await database.connect();

const adminEmail = `${shortid.generate().toLowerCase()}@test.com`;
adminId = await getUserIdFromSignUp(adminEmail);

const organization = new Organization({
name: 'tenant organization',
description: 'testing org',
isPublic: true,
visibileInSearch: true,
status: 'ACTIVE',
members: [adminId],
admins: [adminId],
posts: [],
membershipRequests: [],
blockedUsers: [],
groupChats: [],
image: '',
creator: adminId,
});
const savedOrg = await organization.save();
organizationId = savedOrg._id;

const admin = await User.findById(adminId);
admin.overwrite({
...admin._doc,
joinedOrganizations: [organizationId],
createdOrganizations: [organizationId],
adminFor: [organizationId],
});
await admin.save();

const tenant = new Tenant({
organization: organizationId,
url: tenantUrl,
});
await tenant.save();
});

afterAll(async () => {
const conn1 = connectionManager.getTenantConnection(organizationId);
const conn2 = connectionManager.getTenantConnection(secondOrganizationId);
await conn1.Post.deleteMany();
await conn2.Post.deleteMany();
await User.findByIdAndDelete(adminId);
await Organization.findByIdAndDelete(organizationId);
await Organization.findByIdAndDelete(secondOrganizationId);
await Tenant.deleteMany({});
await connectionManager.destroyConnections();
await database.disconnect();
});

describe('tenant is working and transparent from main db', () => {
test('initTenants and destroyConnections', async () => {
let conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBe(null);
await connectionManager.initTenants();
conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBeTruthy();
await connectionManager.destroyConnections();
conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBe(null);
await connectionManager.initTenants();
});
test('addConnection', async () => {
const organization = new Organization({
name: 'second tenant organization',
description: 'testing org',
isPublic: true,
visibileInSearch: true,
status: 'ACTIVE',
members: [adminId],
admins: [adminId],
posts: [],
membershipRequests: [],
blockedUsers: [],
groupChats: [],
image: '',
creator: adminId,
});

const savedOrg = await organization.save();
secondOrganizationId = savedOrg._id;

const admin = await User.findById(adminId);
admin.overwrite({
...admin._doc,
joinedOrganizations: [organizationId, secondOrganizationId],
createdOrganizations: [organizationId, secondOrganizationId],
adminFor: [organizationId, secondOrganizationId],
});
await admin.save();
const tenant = new Tenant({
organization: secondOrganizationId,
url: secondTenantUrl,
});
await tenant.save();

const conn = await connectionManager.addTenantConnection(
secondOrganizationId
);
expect(conn).toBeTruthy();
const posts = await conn.Post.find();
expect(posts).toEqual([]);
});

test('getConnection', async () => {
const conn = connectionManager.getTenantConnection(organizationId);
const newPost = new conn.Post({
status: 'ACTIVE',
likedBy: [adminId],
likeCount: 1,
comments: [],
text: 'a',
title: 'a',
imageUrl: 'a.png',
videoUrl: 'a',
creator: adminId,
organization: organizationId,
});
await newPost.save();
const [savedPost] = await conn.Post.find();
expect(savedPost).toBeTruthy();
});

test('Isolated tenants', async () => {
const conn1 = connectionManager.getTenantConnection(organizationId);
const conn2 = connectionManager.getTenantConnection(secondOrganizationId);

const firstOrgPosts = await conn1.Post.find();
const secondOrgPosts = await conn2.Post.find();

expect(firstOrgPosts).toHaveLength(1);
expect(secondOrgPosts).toHaveLength(0);
});
});

0 comments on commit 80318d3

Please sign in to comment.