Skip to content

Commit

Permalink
Merge pull request #738 from ebrahim354/updated-multi-tenancy
Browse files Browse the repository at this point in the history
added connection manager
  • Loading branch information
JamaicanFriedChicken authored Aug 4, 2022
2 parents ae9d1d0 + 3f0a835 commit dbb8271
Show file tree
Hide file tree
Showing 24 changed files with 2,278 additions and 144 deletions.
16 changes: 16 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@ REFRESH_TOKEN_SECRET=

MONGO_DB_URL=

# Replace these values with instructions from INSTALLATION.md
# androidFirebaseOptions
apiKey=AIzaSyBkqgTkg2yNRsl7jIx_EtCyF9YqiCJX7sz
appId=1:803598513727:android:20d58bd8c9aae2102c2a8a
messagingSenderId=803598513727
projectId=talawa-352607
storageBucket=talawa-352607.appspot.com

# iosFirebaseOptions
iOSapiKey=AIzaSyCw_AKsWnW6oqUq4CC41aDmFcPuLkLO0gc
iOSappId=1:803598513727:ios:1f06f9c8eef964b42c2a8a
iOSmessagingSenderId=803598513727
iOSprojectId=talawa-352607
iOSstorageBucket=talawa-352607.appspot.com
iosClientId=803598513727-2gt176dpe0ljn5ie967o4d0rm0vo8sm0.apps.googleusercontent.com
iosBundleId=com.talawa.app
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ yarn.lock

# We don't want to have code coverage data stored in the repo as it is dynamic
coverage/

serviceAccountKey.json
29 changes: 28 additions & 1 deletion INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,34 @@ Follow these steps to get the API running.
npm install
10. Start the `talawa-api` server using the below command.
10. To test the notification service create a new firebase project:
- In the Firebase console, open Settings > [Service Accounts](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk).
- Click Generate New Private Key, then confirm by clicking Generate Key.
- Securely store the JSON file containing the key.
- Run the following command to set the key in the environment variable:
- Linux/macOS: `export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"`
- Windows: `$env:GOOGLE_APPLICATION_CREDENTIALS="C:\Users\username\Downloads\service-account-file.json"`
-
1. Install the [Firebase CLI](https://firebase.google.com/docs/cli#install_the_firebase_cli).
2. Copy the `firebase_options.dart` file as it will be modified.
3. Run the following commands in the project directory of talawa mobile app:
- `firebase login`
- `dart pub global activate flutterfire_cli`
- `flutterfire configure`
4. Select the project you created in the Firebase console in step 2.
5. Add iOS and android platforms to the project.
6. Overwrite the `firebase_options.dart` file if asked so.
7. Copy the keys to `.env` file, for how to set keys refer to `.env.sample` file.
8. Undo the changes made to the `firebase_options.dart` file by pasting the old content from step 2.
11. Start the `talawa-api` server using the below command in the same terminal.
npm start
Expand Down
17 changes: 17 additions & 0 deletions firebaseOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports.androidFirebaseOptions = {
apiKey: process.env.apiKey,
appId: process.env.appId,
messagingSenderId: process.env.messagingSenderId,
projectId: process.env.projectId,
storageBucket: process.env.storageBucket,
};

module.exports.iosFirebaseOptions = {
apiKey: process.env.iOSapiKey,
appId: process.env.iOSappId,
messagingSenderId: process.env.iOSmessagingSenderId,
projectId: process.env.iOSprojectId,
storageBucket: process.env.iOSstorageBucket,
iosClientId: process.env.iosClientId,
iosBundleId: process.env.iosBundleId,
};
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);
4 changes: 4 additions & 0 deletions lib/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const userSchema = new Schema({
type: String,
required: true,
},
token: {
type: String,
required: false,
},
lastName: {
type: String,
required: true,
Expand Down
4 changes: 4 additions & 0 deletions lib/resolvers/Mutation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const signUp = require('./auth_mutations/signup');
const login = require('./auth_mutations/login');
const saveFcmToken = require('./auth_mutations/saveFcmToken');
const logout = require('./auth_mutations/logout');
const refreshToken = require('./auth_mutations/refresh_token');
const revokeRefreshTokenForUser = require('./auth_mutations/revoke_refresh_token_for_user');

Expand Down Expand Up @@ -68,6 +70,8 @@ const addLanguageTranslation = require('./language_maintainer_mutation/addLangua
const Mutation = {
signUp,
login,
saveFcmToken,
logout,
refreshToken,
revokeRefreshTokenForUser,
updateLanguage,
Expand Down
6 changes: 6 additions & 0 deletions lib/resolvers/auth_mutations/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const {
const { NotFoundError, ValidationError } = require('errors');
const requestContext = require('talawa-request-context');
const copyToClipboard = require('../functions/copyToClipboard');
const {
androidFirebaseOptions,
iosFirebaseOptions,
} = require('../../../firebaseOptions');

module.exports = async (parent, args) => {
const user = await User.findOne({ email: args.data.email.toLowerCase() })
Expand Down Expand Up @@ -57,5 +61,7 @@ module.exports = async (parent, args) => {
},
accessToken,
refreshToken,
androidFirebaseOptions,
iosFirebaseOptions,
};
};
25 changes: 25 additions & 0 deletions lib/resolvers/auth_mutations/logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const User = require('../../models/User');
const { NotFoundError } = require('errors');
const requestContext = require('talawa-request-context');
const {
USER_NOT_FOUND_MESSAGE,
USER_NOT_FOUND_CODE,
USER_NOT_FOUND_PARAM,
} = require('../../../constants');

module.exports = async (parent, args, context) => {
const user = await User.findOne({ _id: context.userId });
if (!user) {
throw new NotFoundError(
requestContext.translate(USER_NOT_FOUND_MESSAGE),
USER_NOT_FOUND_CODE,
USER_NOT_FOUND_PARAM
);
}

user.token = null;

await user.save();

return true;
};
25 changes: 25 additions & 0 deletions lib/resolvers/auth_mutations/saveFcmToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const User = require('../../models/User');
const { NotFoundError } = require('errors');
const requestContext = require('talawa-request-context');
const {
USER_NOT_FOUND_MESSAGE,
USER_NOT_FOUND_CODE,
USER_NOT_FOUND_PARAM,
} = require('../../../constants');

module.exports = async (parent, args, context) => {
const user = await User.findById(context.userId);
if (!user) {
throw new NotFoundError(
requestContext.translate(USER_NOT_FOUND_MESSAGE),
USER_NOT_FOUND_CODE,
USER_NOT_FOUND_PARAM
);
}

user.token = args.token;

await user.save();

return true;
};
19 changes: 19 additions & 0 deletions lib/resolvers/event_mutations/createEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const {
ORGANIZATION_NOT_AUTHORIZED_CODE,
ORGANIZATION_NOT_AUTHORIZED_PARAM,
} = require('../../../constants');
var admin = require('firebase-admin');
var { applicationDefault } = require('firebase-admin').credential;

admin.initializeApp({ credential: applicationDefault() });

const createEvent = async (parent, args, context) => {
const user = await User.findOne({ _id: context.userId });
Expand Down Expand Up @@ -93,6 +97,21 @@ const createEvent = async (parent, args, context) => {
}
);

const members = org.members;
for (let i = 0; i < members.length; i++) {
const member = members[i];
const memberUser = await User.findOne({ _id: member });
if (memberUser && memberUser.token) {
await admin.messaging().send({
token: memberUser.token,
notification: {
title: 'New Event',
body: `${user.firstName} has created a new event in ${org.name}`,
},
});
}
}

return {
...newEvent._doc,
};
Expand Down
2 changes: 2 additions & 0 deletions lib/schema/mutation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module.exports = `
type Mutation {
signUp(data: UserInput!, file:Upload): AuthData!
login(data: LoginInput!): AuthData!
saveFcmToken(token: String) : Boolean! @auth
refreshToken(refreshToken: String!) : ExtendSession!
revokeRefreshTokenForUser(userId: String!) : Boolean!
updateLanguage(languageCode: String!) : User! @auth
logout: Boolean! @auth

updateUserProfile(data: UpdateUserInput, file:Upload): User! @auth
createEvent(data: EventInput): Event! @auth
Expand Down
20 changes: 20 additions & 0 deletions lib/schema/user/auth.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@ module.exports = `
password:String!
}

type AndroidFirebaseOptions {
apiKey: String,
appId: String,
messagingSenderId: String,
projectId: String,
storageBucket: String,
}

type IOSFirebaseOptions {
apiKey: String,
appId: String,
messagingSenderId: String,
projectId: String,
storageBucket: String,
iosClientId: String,
iosBundleId: String,
}

type AuthData {
user: User!,
accessToken: String!
refreshToken: String!
androidFirebaseOptions: AndroidFirebaseOptions!
iosFirebaseOptions: IOSFirebaseOptions!
}

type ExtendSession {
Expand Down
Loading

0 comments on commit dbb8271

Please sign in to comment.