diff --git a/constants.js b/constants.js index a671c50cc5..71d1264773 100644 --- a/constants.js +++ b/constants.js @@ -31,6 +31,8 @@ const ORGANIZATION_NOT_FOUND_MESSAGE = 'organization.notFound'; const ORGANIZATION_NOT_FOUND_CODE = 'organization.notFound'; const ORGANIZATION_NOT_FOUND_PARAM = 'organization'; +const CONNECTION_NOT_FOUND = 'Connection not found'; + const ORGANIZATION_MEMBER_NOT_FOUND = "Organization's user is not a member"; const ORGANIZATION_MEMBER_NOT_FOUND_MESSAGE = 'organization.member.notFound'; const ORGANIZATION_MEMBER_NOT_FOUND_CODE = 'organization.member.notFound'; @@ -85,9 +87,12 @@ const POST_NOT_FOUND_MESSAGE = 'post.notFound'; const POST_NOT_FOUND_CODE = 'post.notFound'; const POST_NOT_FOUND_PARAM = 'post'; +const DATABASE_CONNECTION_FAIL = 'Failed to connect to database'; + const STATUS_ACTIVE = 'ACTIVE'; const IN_PRODUCTION = process.env.NODE_ENV === 'production'; +const IN_TEST = process.env.NODE_ENV === 'test'; if (process.env.NODE_ENV === 'test') { URL = 'http://localhost:4000/graphql'; @@ -98,6 +103,7 @@ module.exports = { URL, IN_PRODUCTION, + IN_TEST, USER_NOT_AUTHORIZED, USER_NOT_AUTHORIZED_MESSAGE, @@ -114,6 +120,8 @@ module.exports = { ORGANIZATION_NOT_FOUND_CODE, ORGANIZATION_NOT_FOUND_PARAM, + CONNECTION_NOT_FOUND, + EVENT_PROJECT_NOT_FOUND, EVENT_PROJECT_NOT_FOUND_CODE, EVENT_PROJECT_NOT_FOUND_MESSAGE, @@ -182,4 +190,6 @@ module.exports = { POST_NOT_FOUND_MESSAGE, POST_NOT_FOUND_CODE, POST_NOT_FOUND_PARAM, + + DATABASE_CONNECTION_FAIL, }; diff --git a/db.js b/db.js index afab53717a..f983de0554 100644 --- a/db.js +++ b/db.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose'); const logger = require('logger'); +const destroyConnections = require('./lib/ConnectionManager/destroyConnections'); const connect = async () => { try { @@ -16,6 +17,7 @@ const connect = async () => { }; const disconnect = async () => { + await destroyConnections(); await mongoose.connection.close(); }; module.exports = { connect, disconnect }; diff --git a/index.js b/index.js index 07b60698c7..e95fe62716 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ require('dotenv').config(); // pull env variables from .env file +const connectionManager = require('./lib/ConnectionManager'); const depthLimit = require('graphql-depth-limit'); const { ApolloServer, PubSub } = require('apollo-server-express'); const http = require('http'); @@ -113,7 +114,7 @@ const apolloServer = new ApolloServer({ auth: AuthenticationDirective, role: RoleAuthorizationDirective, }, - context: ({ req, res, connection }) => { + context: async ({ req, res, connection }) => { if (connection) { return { ...connection, @@ -172,6 +173,7 @@ apolloServer.installSubscriptionHandlers(httpServer); const serverStart = async () => { try { await database.connect(); + await connectionManager.initTenants(); httpServer.listen(process.env.PORT || 4000, () => { logger.info( `🚀 Server ready at http://localhost:${process.env.PORT || 4000}${ diff --git a/lib/ConnectionManager/README.md b/lib/ConnectionManager/README.md new file mode 100644 index 0000000000..51d6961ebe --- /dev/null +++ b/lib/ConnectionManager/README.md @@ -0,0 +1,48 @@ +# Connection Manager Module + +This is the module that is responsible for connecting/disconnecting to each tenant's database. + +### Prototype : + +`addTenantConnection(organizationId)` + +`getTenantConnection(organizationId)` + +`initTenants()` + +`destroyConnections()` + +`addTenantConnection` is responsible for connecting to the database of a specific organization `organizationId` +and returning a reference to the connection object if the organization is not found it will throw an organization not found error. + +`getTenantConnection` is responsible for retrivnig a connection to `organizationId` which already is in memory if it's not the function will throw a connection not found error. + +`initTenants` is usually used on the startup of the api which connects to all the available tenants that are stored. + +`destoryConnections` is used to close the connections and deleting them from memory. + +### Basic usage : + +This is mostly how it's going to be used: + +```javascript +const connection = getTenantConnection(organizationId); +const Post = connection.Post; + +// posts is an array of the posts stored in that specific tenant. +const posts = await Post.find({}); +// to close connections: +await destroyConnections(); +// would throw an error: +const con = getTenantConnection(organizationId); +``` + +### This is the main way to manage the databases that are devided into 2 types: + +##### main database: + +which holds connections, organizations, users, files data (shared data). + +##### tenant databases; + +which holds organization specific data such as posts, events, comments... diff --git a/lib/ConnectionManager/addTenantConnection.js b/lib/ConnectionManager/addTenantConnection.js new file mode 100644 index 0000000000..f3d706004c --- /dev/null +++ b/lib/ConnectionManager/addTenantConnection.js @@ -0,0 +1,31 @@ +const Tenant = require('../models/Tenant'); +const Database = require('../Database/index'); +const { setConnection } = require('./connections'); +const { NotFoundError } = require('errors'); +const requestContext = require('talawa-request-context'); +const { + ORGANIZATION_NOT_FOUND, + ORGANIZATION_NOT_FOUND_MESSAGE, + ORGANIZATION_NOT_FOUND_PARAM, + ORGANIZATION_NOT_FOUND_CODE, + IN_PRODUCTION, +} = require('../../constants'); + +module.exports = async (organizationId) => { + const [tenant] = await Tenant.find({ organization: organizationId }); + if (!tenant || !tenant.url) { + throw new NotFoundError( + !IN_PRODUCTION + ? ORGANIZATION_NOT_FOUND + : requestContext.translate(ORGANIZATION_NOT_FOUND_MESSAGE), + ORGANIZATION_NOT_FOUND_CODE, + ORGANIZATION_NOT_FOUND_PARAM + ); + } + let connection = new Database(tenant.url, { + schema: !tenant.scheme || tenant.scheme.length === 0 ? [] : tenant.scheme, + }); + await connection.connect(); + setConnection(tenant.organization, connection); + return connection; +}; diff --git a/lib/ConnectionManager/connections.js b/lib/ConnectionManager/connections.js new file mode 100644 index 0000000000..0a2d5c00b1 --- /dev/null +++ b/lib/ConnectionManager/connections.js @@ -0,0 +1,57 @@ +const Tenant = require('../../lib/models/Tenant'); +const Database = require('../Database/index'); + +const connections = {}; +const MainDB = require('../../lib/models'); + +const setConnection = (orgId, connection) => { + connections[`${orgId}`] = connection; +}; + +const getConnection = async (orgId) => { + if (!connections[orgId]) { + const [tenant] = await Tenant.find({ organization: orgId }); + if (!tenant || !tenant.url) { + return MainDB; + } + let connection = new Database(tenant.url, { + schema: !tenant.scheme || tenant.scheme.length === 0 ? [] : tenant.scheme, + }); + await connection.connect(); + setConnection(tenant.organization, connection); + return connection; + } + return connections[orgId]; +}; + +const getAllConnections = () => { + return connections; +}; + +const orgHasTenant = (orgId) => { + if (connections[orgId]) return true; + return false; +}; + +const destroy = async () => { + for (let conn in connections) { + await connections[conn].disconnect(); + delete connections[conn]; + } +}; + +const destroyOneConnection = async (orgId) => { + if (!connections[orgId]) return false; + await connections[orgId].disconnect(); + delete connections[orgId]; + return true; +}; + +module.exports = { + setConnection, + getConnection, + destroy, + getAllConnections, + orgHasTenant, + destroyOneConnection, +}; diff --git a/lib/ConnectionManager/destroyConnections.js b/lib/ConnectionManager/destroyConnections.js new file mode 100644 index 0000000000..635567fda2 --- /dev/null +++ b/lib/ConnectionManager/destroyConnections.js @@ -0,0 +1,5 @@ +const connections = require('./connections'); + +module.exports = async () => { + await connections.destroy(); +}; diff --git a/lib/ConnectionManager/destroyOneConnection.js b/lib/ConnectionManager/destroyOneConnection.js new file mode 100644 index 0000000000..3618315cba --- /dev/null +++ b/lib/ConnectionManager/destroyOneConnection.js @@ -0,0 +1,5 @@ +const connections = require('./connections'); + +module.exports = async (orgId) => { + return await connections.destroyOneConnection(orgId); +}; diff --git a/lib/ConnectionManager/getTenantConnection.js b/lib/ConnectionManager/getTenantConnection.js new file mode 100644 index 0000000000..48be0b46da --- /dev/null +++ b/lib/ConnectionManager/getTenantConnection.js @@ -0,0 +1,5 @@ +const { getConnection } = require('./connections'); + +module.exports = async (organizationId) => { + return await getConnection(organizationId); +}; diff --git a/lib/ConnectionManager/index.js b/lib/ConnectionManager/index.js new file mode 100644 index 0000000000..37f4fb00ba --- /dev/null +++ b/lib/ConnectionManager/index.js @@ -0,0 +1,13 @@ +const addTenantConnection = require('./addTenantConnection'); +const getTenantConnection = require('./getTenantConnection'); +const initTenants = require('./initTenants'); +const destroyConnections = require('./destroyConnections'); +const destroyOneConnection = require('./destroyOneConnection'); + +module.exports = { + addTenantConnection, + getTenantConnection, + initTenants, + destroyConnections, + destroyOneConnection, +}; diff --git a/lib/ConnectionManager/initTenants.js b/lib/ConnectionManager/initTenants.js new file mode 100644 index 0000000000..b355b592e9 --- /dev/null +++ b/lib/ConnectionManager/initTenants.js @@ -0,0 +1,17 @@ +const Tenant = require('../models/Tenant'); +const logger = require('logger'); +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, { schema: db.scheme }); + await connection.connect(); + setConnection(db.organization, connection); + } + } catch (e) { + logger.error('Error while connecting to mongo database', e); + } +}; diff --git a/lib/Database/MongoImplementation/index.js b/lib/Database/MongoImplementation/index.js new file mode 100644 index 0000000000..cd387042ab --- /dev/null +++ b/lib/Database/MongoImplementation/index.js @@ -0,0 +1,86 @@ +const MongooseInstance = require('mongoose').Mongoose; +const { DATABASE_CONNECTION_FAIL } = require('../../../constants'); +const MessageChat = require('./schema/Chat'); +const Comment = require('./schema/Comment'); +const DirectChat = require('./schema/DirectChat'); +const DirectChatMessage = require('./schema/DirectChatMessage'); +const Event = require('./schema/Event'); +const EventProject = require('./schema/EventProject'); +const File = require('./schema/File'); +const Group = require('./schema/Group'); +const GroupChat = require('./schema/GroupChat'); +const GroupChatMessage = require('./schema/GroupChatMessage'); +const ImageHash = require('./schema/ImageHash'); +const Language = require('./schema/Language'); +const MembershipRequest = require('./schema/MembershipRequest'); +const Message = require('./schema/Message'); +const Organization = require('./schema/Organization'); +const Plugin = require('./schema/Plugin'); +const PluginField = require('./schema/PluginField'); +const Post = require('./schema/Post'); +const Task = require('./schema/Task'); +const User = require('./schema/User'); + +const defaultSchema = { + MessageChat, + Comment, + DirectChat, + DirectChatMessage, + Event, + EventProject, + File, + Group, + GroupChat, + GroupChatMessage, + ImageHash, + Language, + MembershipRequest, + Message, + Organization, + Plugin, + PluginField, + Post, + Task, + User, +}; + +function MongoDB(url, schemaObjects) { + this.mongo = new MongooseInstance(); + // this.schema = schemaObjects ? schemaObjects : defaultSchema; + if (!schemaObjects) { + let schemaObj = {}; + for (let obj of schemaObjects) { + if (defaultSchema[obj]) { + schemaObj[obj] = defaultSchema[obj]; + } + } + this.schema = schemaObj; + } else { + this.schema = defaultSchema; + } + + this.type = 'Mongoose'; + this.Native = this.mongo.connection.db; + this.connect = async () => { + try { + await this.mongo.connect(url, { + useCreateIndex: true, + useUnifiedTopology: true, + useFindAndModify: false, + useNewUrlParser: true, + }); + this.Native = this.mongo.connection.db; + } catch (error) { + throw new Error(DATABASE_CONNECTION_FAIL); + } + }; + this.disconnect = async () => { + await this.mongo.connection.close(); + }; + // creating the schema + for (let key in this.schema) { + this[key] = this.mongo.model(key, this.schema[key]); + } +} + +module.exports = MongoDB; diff --git a/lib/Database/MongoImplementation/schema/Chat.js b/lib/Database/MongoImplementation/schema/Chat.js new file mode 100644 index 0000000000..6c3eeb70ad --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Chat.js @@ -0,0 +1,30 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const chatSchema = new Schema({ + message: { + type: String, + required: true, + }, + languageBarrier: { + type: Boolean, + required: false, + default: false, + }, + sender: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + receiver: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, +}); +module.exports = chatSchema; diff --git a/lib/Database/MongoImplementation/schema/Comment.js b/lib/Database/MongoImplementation/schema/Comment.js new file mode 100644 index 0000000000..c8c61dccac --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Comment.js @@ -0,0 +1,42 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +//this is the Structure of the Comments +const commentSchema = new Schema({ + text: { + type: String, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + post: { + type: Schema.Types.ObjectId, + ref: 'Post', + required: true, + }, + likedBy: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + }, + ], + likeCount: { + type: Number, + default: 0, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = commentSchema; diff --git a/lib/Database/MongoImplementation/schema/DirectChat.js b/lib/Database/MongoImplementation/schema/DirectChat.js new file mode 100644 index 0000000000..98b386662d --- /dev/null +++ b/lib/Database/MongoImplementation/schema/DirectChat.js @@ -0,0 +1,38 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +//this is the Structure of the direct chat +const directChatSchema = new Schema({ + users: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], + messages: [ + { + type: Schema.Types.ObjectId, + ref: 'DirectChatMessage', + }, + ], + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = directChatSchema; diff --git a/lib/Database/MongoImplementation/schema/DirectChatMessage.js b/lib/Database/MongoImplementation/schema/DirectChatMessage.js new file mode 100644 index 0000000000..d23399c974 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/DirectChatMessage.js @@ -0,0 +1,38 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +//this is the Structure of the Direct chats +const directChatMessageSchema = new Schema({ + directChatMessageBelongsTo: { + type: Schema.Types.ObjectId, + ref: 'DirectChat', + required: true, + }, + sender: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + receiver: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + createdAt: { + type: Date, + required: true, + }, + messageContent: { + type: String, + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = directChatMessageSchema; diff --git a/lib/Database/MongoImplementation/schema/Event.js b/lib/Database/MongoImplementation/schema/Event.js new file mode 100644 index 0000000000..77a25b91f0 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Event.js @@ -0,0 +1,109 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +const UserAttendes = require('./UserAttendes'); + +//this is the Structure of the event +const eventSchema = new Schema({ + title: { + type: String, + required: true, + }, + description: { + type: String, + required: true, + }, + attendees: { + type: String, + required: false, + }, + location: { + type: String, + }, + latitude: { + type: Number, + required: false, + }, + longitude: { + type: Number, + required: false, + }, + recurring: { + type: Boolean, + required: true, + default: false, + }, + allDay: { + type: Boolean, + required: true, + }, + startDate: { + type: String, + required: true, + }, + endDate: { + type: String, + required: function () { + return !this.allDay; + }, + }, + startTime: { + type: String, + required: function () { + return !this.allDay; + }, + }, + endTime: { + type: String, + required: function () { + return !this.allDay; + }, + }, + recurrance: { + type: String, + default: 'ONCE', + enum: ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY', 'ONCE'], + required: function () { + return this.recurring; + }, + }, + isPublic: { + type: Boolean, + required: true, + }, + isRegisterable: { + type: Boolean, + required: true, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + registrants: [UserAttendes], + admins: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + tasks: [ + { + type: Schema.Types.ObjectId, + ref: 'Task', + }, + ], + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = eventSchema; diff --git a/lib/Database/MongoImplementation/schema/EventProject.js b/lib/Database/MongoImplementation/schema/EventProject.js new file mode 100644 index 0000000000..8b3539eae5 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/EventProject.js @@ -0,0 +1,43 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +//this is the Structure of the event project +const eventProjectSchema = new Schema({ + title: { + type: String, + required: true, + }, + description: { + type: String, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, + event: { + type: Schema.Types.ObjectId, + ref: 'Event', + required: true, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + tasks: [ + { + type: Schema.Types.ObjectId, + ref: 'Task', + }, + ], + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = eventProjectSchema; diff --git a/lib/Database/MongoImplementation/schema/File.js b/lib/Database/MongoImplementation/schema/File.js new file mode 100644 index 0000000000..75749c177b --- /dev/null +++ b/lib/Database/MongoImplementation/schema/File.js @@ -0,0 +1,37 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +const { v4: uuidv4 } = require('uuid'); + +const fileSchema = new Schema({ + name: { + type: String, + required: true, + default: uuidv4(), + }, + url: { + type: String, + }, + size: { + type: Number, + }, + secret: { + type: String, + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + contentType: { + type: String, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = fileSchema; diff --git a/lib/Database/MongoImplementation/schema/Group.js b/lib/Database/MongoImplementation/schema/Group.js new file mode 100644 index 0000000000..179bd7f84c --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Group.js @@ -0,0 +1,37 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const groupSchema = new Schema({ + title: { + type: String, + required: true, + }, + description: { + type: String, + }, + createdAt: { + type: Date, + default: Date.now, + }, + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + admins: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], +}); + +module.exports = groupSchema; diff --git a/lib/Database/MongoImplementation/schema/GroupChat.js b/lib/Database/MongoImplementation/schema/GroupChat.js new file mode 100644 index 0000000000..25d4cfe851 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/GroupChat.js @@ -0,0 +1,41 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const groupChatSchema = new Schema({ + title: { + type: String, + required: true, + }, + users: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], + messages: [ + { + type: Schema.Types.ObjectId, + ref: 'GroupChatMessage', + }, + ], + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = groupChatSchema; diff --git a/lib/Database/MongoImplementation/schema/GroupChatMessage.js b/lib/Database/MongoImplementation/schema/GroupChatMessage.js new file mode 100644 index 0000000000..f410181034 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/GroupChatMessage.js @@ -0,0 +1,32 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const groupChatMessageSchema = new Schema({ + groupChatMessageBelongsTo: { + type: Schema.Types.ObjectId, + ref: 'GroupChat', + required: true, + }, + sender: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + createdAt: { + type: Date, + required: true, + }, + messageContent: { + type: String, + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = groupChatMessageSchema; diff --git a/lib/Database/MongoImplementation/schema/ImageHash.js b/lib/Database/MongoImplementation/schema/ImageHash.js new file mode 100644 index 0000000000..0e725cfd69 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/ImageHash.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const imageHashSchema = new Schema({ + hashValue: { + type: String, + required: true, + }, + fileName: { + type: String, + required: true, + }, + numberOfUses: { + type: Number, + default: 0, + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = imageHashSchema; diff --git a/lib/Database/MongoImplementation/schema/Language.js b/lib/Database/MongoImplementation/schema/Language.js new file mode 100644 index 0000000000..9e09212a08 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Language.js @@ -0,0 +1,43 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LangModel = new Schema({ + lang_code: { + type: String, + required: true, + unique: false, + lowercase: true, + }, + value: { + type: String, + required: true, + lowercase: true, + }, + verified: { + type: Boolean, + required: true, + default: false, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +const LangSchema = new Schema({ + en: { + type: String, + required: true, + unique: true, + lowercase: true, + }, + translation: [LangModel], + createdAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +module.exports = LangSchema; diff --git a/lib/Database/MongoImplementation/schema/MembershipRequest.js b/lib/Database/MongoImplementation/schema/MembershipRequest.js new file mode 100644 index 0000000000..2fda37ecec --- /dev/null +++ b/lib/Database/MongoImplementation/schema/MembershipRequest.js @@ -0,0 +1,23 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const membershipRequestSchema = new Schema({ + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + user: { + type: Schema.Types.ObjectId, + ref: 'User', + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = membershipRequestSchema; diff --git a/lib/Database/MongoImplementation/schema/Message.js b/lib/Database/MongoImplementation/schema/Message.js new file mode 100644 index 0000000000..985ee9e9db --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Message.js @@ -0,0 +1,39 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const messageSchema = new Schema({ + text: { + type: String, + required: true, + }, + imageUrl: { + type: String, + required: false, + }, + videoUrl: { + type: String, + required: false, + }, + createdAt: { + type: Date, + default: Date.now, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + group: { + type: Schema.Types.ObjectId, + ref: 'Group', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, +}); + +module.exports = messageSchema; diff --git a/lib/Database/MongoImplementation/schema/Organization.js b/lib/Database/MongoImplementation/schema/Organization.js new file mode 100644 index 0000000000..75deadbad7 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Organization.js @@ -0,0 +1,88 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const organizationSchema = new Schema({ + apiUrl: { + type: String, + }, + image: { + type: String, + }, + name: { + type: String, + required: true, + }, + description: { + type: String, + required: true, + }, + location: { + type: String, + }, + isPublic: { + type: Boolean, + required: true, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + members: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + }, + ], + admins: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], + groupChats: [ + { + type: Schema.Types.ObjectId, + ref: 'Message', + }, + ], + posts: [ + { + type: Schema.Types.ObjectId, + ref: 'Post', + }, + ], + membershipRequests: [ + { + type: Schema.Types.ObjectId, + ref: 'MembershipRequest', + }, + ], + blockedUsers: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + }, + ], + tags: [], + functionality: { + type: [String], + required: true, + default: ['DirectChat', 'Posts', 'Events', 'Groups', 'GroupChats'], + }, + visibleInSearch: Boolean, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +module.exports = organizationSchema; diff --git a/lib/Database/MongoImplementation/schema/Plugin.js b/lib/Database/MongoImplementation/schema/Plugin.js new file mode 100644 index 0000000000..cd346832b6 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Plugin.js @@ -0,0 +1,35 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +/** + * @name pluginSchema + * @description Schema for MongoDB database + * @param {string} pluginName Name of the plugin preferred having underscores "_" + * @param {string} pluginCreatedBy name of the plugin creator ex.John Doe + * @param {string} pluginDesc brief description of the plugin and it's features + * @param {Boolean} pluginInstallStatus shows if the plugin is enabled or not + * @param {String[]} installedOrgs list of orgIDs on which the plugin is enabled + */ +const pluginSchema = new Schema({ + pluginName: { + type: String, + required: true, + }, + pluginCreatedBy: { + type: String, + required: true, + }, + pluginDesc: { + type: String, + required: true, + }, + pluginInstallStatus: { + type: Boolean, + required: true, + default: false, + }, + installedOrgs: [ + { type: Schema.Types.ObjectId, required: false, default: [] }, + ], +}); + +module.exports = pluginSchema; diff --git a/lib/Database/MongoImplementation/schema/PluginField.js b/lib/Database/MongoImplementation/schema/PluginField.js new file mode 100644 index 0000000000..51a2a519cf --- /dev/null +++ b/lib/Database/MongoImplementation/schema/PluginField.js @@ -0,0 +1,26 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +//this is the Structure of the Comments +const pluginFieldSchema = new Schema({ + key: { + type: String, + required: true, + }, + value: { + type: String, + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +module.exports = pluginFieldSchema; diff --git a/lib/Database/MongoImplementation/schema/Post.js b/lib/Database/MongoImplementation/schema/Post.js new file mode 100644 index 0000000000..7341e96992 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Post.js @@ -0,0 +1,68 @@ +/* eslint-disable prettier/prettier */ +const mongoose = require('mongoose'); +const mongoosePaginate = require('mongoose-paginate-v2'); +const User = require('../../../models/User'); + +const Schema = mongoose.Schema; + +const postSchema = new Schema({ + text: { + type: String, + required: true, + }, + title: { + type: String, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + createdAt: { + type: Date, + default: Date.now, + }, + imageUrl: { + type: String, + required: false, + }, + videoUrl: { + type: String, + required: false, + }, + creator: { + type: Schema.Types.ObjectId, + ref: User, + required: true, + }, + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization', + required: true, + }, + likedBy: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + }, + ], + comments: [ + { + type: Schema.Types.ObjectId, + ref: 'Comment', + }, + ], + likeCount: { + type: Number, + default: 0, + }, + commentCount: { + type: Number, + default: 0, + }, +}); + +postSchema.plugin(mongoosePaginate); + +module.exports = postSchema; diff --git a/lib/Database/MongoImplementation/schema/Task.js b/lib/Database/MongoImplementation/schema/Task.js new file mode 100644 index 0000000000..12b10c63e7 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/Task.js @@ -0,0 +1,33 @@ +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const taskSchema = new Schema({ + title: { + type: String, + required: true, + }, + description: { + type: String, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + createdAt: { type: Date, default: Date.now }, + deadline: { type: Date }, + event: { + type: Schema.Types.ObjectId, + ref: 'Event', + required: true, + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, +}); + +module.exports = taskSchema; diff --git a/lib/Database/MongoImplementation/schema/User.js b/lib/Database/MongoImplementation/schema/User.js new file mode 100644 index 0000000000..dab0cb2f85 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/User.js @@ -0,0 +1,120 @@ +const mongoose = require('mongoose'); +const { isEmail } = require('validator'); +const mongoosePaginate = require('mongoose-paginate-v2'); +const Schema = mongoose.Schema; + +const userSchema = new Schema({ + image: { + type: String, + }, + tokenVersion: { + type: Number, + default: 0, + }, + firstName: { + type: String, + required: true, + }, + token: { + type: String, + required: false, + }, + lastName: { + type: String, + required: true, + }, + email: { + type: String, + validate: [isEmail, 'invalid email'], + required: true, + }, + password: { + type: String, + required: true, + }, + appLanguageCode: { + type: String, + default: 'en', + required: true, + }, + createdOrganizations: [ + { + type: Schema.Types.ObjectId, + ref: 'Organization', + }, + ], + createdEvents: [ + { + type: Schema.Types.ObjectId, + ref: 'Event', + }, + ], + userType: { + type: String, + enum: ['USER', 'ADMIN', 'SUPERADMIN'], + default: 'USER', + required: true, + }, + joinedOrganizations: [ + { + type: Schema.Types.ObjectId, + ref: 'Organization', + }, + ], + registeredEvents: [ + { + type: Schema.Types.ObjectId, + ref: 'Event', + }, + ], + eventAdmin: [ + { + type: Schema.Types.ObjectId, + ref: 'Event', + }, + ], + adminFor: [ + { + type: Schema.Types.ObjectId, + ref: 'Organization', + }, + ], + membershipRequests: [ + { + type: Schema.Types.ObjectId, + ref: 'MembershipRequest', + }, + ], + organizationsBlockedBy: [ + { + type: Schema.Types.ObjectId, + ref: 'Organization', + }, + ], + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + organizationUserBelongsTo: { + type: Schema.Types.ObjectId, + ref: 'Organization', + }, + pluginCreationAllowed: { + type: Boolean, + required: true, + default: true, + }, + adminApproved: { + type: Boolean, + default: false, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +userSchema.plugin(mongoosePaginate); +module.exports = userSchema; diff --git a/lib/Database/MongoImplementation/schema/UserAttendes.js b/lib/Database/MongoImplementation/schema/UserAttendes.js new file mode 100644 index 0000000000..2b354ab029 --- /dev/null +++ b/lib/Database/MongoImplementation/schema/UserAttendes.js @@ -0,0 +1,26 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const UserAttende = new Schema({ + userId: { + type: String, + required: true, + }, + user: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + status: { + type: String, + required: true, + default: 'ACTIVE', + enum: ['ACTIVE', 'BLOCKED', 'DELETED'], + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +module.exports = UserAttende; diff --git a/lib/Database/README.md b/lib/Database/README.md new file mode 100644 index 0000000000..8f7f4b3643 --- /dev/null +++ b/lib/Database/README.md @@ -0,0 +1,49 @@ +# Database Module + +This folder is a way of making the database usage inside of the API more modular. + +### Prototype : + +`Database(url, options)` + +Where: + +`url` is the connection string of the database that you would like to connect to. + +`options` is an object with the following keys: + +- `schema`: which is an array that contains a set of strings, each string is the name of the record/table you want to add (based on it's file name). + +-- the schema array defaults to all the schema objects that are defined to the system. + +-- more options will be added in the future for example different database types. + +### Basic usage : + +This is mostly how it's going to be used: + +```javascript +const url = 'your-db-connection-url'; +const db = new Database(url); + +// to find all users. +const users = db.User.find(); +``` + +In case of differenct schema in mind here's an example: + +```javascript + const userSchema = require('./schema/userSchema'); + const url = 'your-db-connection-url'; + const db = new Database(url, { + schema: ['user'] + ); + + // to find all users. + const users = db.User.find(); +``` + +### Goal: + +This approach is a way of making the database instantiatable for more functionality such as the multi-tenancy functionality instead of using the static +database object provided by mongoose, we can use the modular opproach or a mix of both modular and static. diff --git a/lib/Database/index.js b/lib/Database/index.js new file mode 100644 index 0000000000..3484a7dce2 --- /dev/null +++ b/lib/Database/index.js @@ -0,0 +1,29 @@ +const MongoDB = require('./MongoImplementation/'); + +function DataBase(url, options) { + /* options{ + schema: { + recordName: Mongoose.Schema + }, + type: "MONGO", "SQL"... + more options could be added later on. + } */ + + this.schema = !options || !options.schema ? [] : options.schema; + this.db = new MongoDB(url, this.schema); + this.url = url; + + // to know that type of the database used. + this.dbType = this.db.type; + this.connect = this.db.connect; + this.disconnect = this.db.disconnect; + // gives access to raw queries of any implementation. + this.NativeDriver = this.db.Native; + this.schema = this.db.schema; + + for (let key in this.schema) { + this[key] = this.db[key]; + } +} + +module.exports = DataBase; diff --git a/lib/helper_functions/addTenantId.js b/lib/helper_functions/addTenantId.js new file mode 100644 index 0000000000..c98bf6eb1e --- /dev/null +++ b/lib/helper_functions/addTenantId.js @@ -0,0 +1,4 @@ +module.exports = (id, tenantId = '', del = ' ') => { + if (!tenantId) return id; + return id + del + tenantId; +}; diff --git a/lib/helper_functions/getTenantFromId.js b/lib/helper_functions/getTenantFromId.js new file mode 100644 index 0000000000..71a3a81f38 --- /dev/null +++ b/lib/helper_functions/getTenantFromId.js @@ -0,0 +1,6 @@ +module.exports = (fullId, del = ' ') => { + if (!fullId) return { id: null, tenantId: null }; + const finalId = '' + fullId; + const [id, tenantId] = finalId.split(del); + return { tenantId, id }; +}; diff --git a/lib/helper_functions/index.js b/lib/helper_functions/index.js new file mode 100644 index 0000000000..51f78f0c5c --- /dev/null +++ b/lib/helper_functions/index.js @@ -0,0 +1,33 @@ +const addTenantId = require('./addTenantId'); +const auth = require('./auth'); +const deleteDuplicatedImage = require('./deleteDuplicatedImage'); +const deleteImage = require('./deleteImage'); +const getTenantFromId = require('./getTenantFromId'); +const imageAlreadyInDbCheck = require('./imageAlreadyInDbCheck'); +const imageExtensionCheck = require('./imageExtensionCheck'); +const mailer = require('./mailer'); +const newDbUrl = require('./newDbUrl'); +const organizationExists = require('./organizationExists'); +const ReuploadDuplicateCheck = require('./ReuploadDuplicateCheck'); +const tenantCtx = require('./tenantCtx'); +const uploadImage = require('./uploadImage'); +const userExists = require('./userExists'); +const orgHasTenant = require('./orgHasTenant'); + +module.exports = { + addTenantId, + auth, + deleteDuplicatedImage, + deleteImage, + getTenantFromId, + imageAlreadyInDbCheck, + imageExtensionCheck, + mailer, + newDbUrl, + organizationExists, + ReuploadDuplicateCheck, + tenantCtx, + uploadImage, + userExists, + orgHasTenant, +}; diff --git a/lib/helper_functions/newDbUrl.js b/lib/helper_functions/newDbUrl.js new file mode 100644 index 0000000000..ca047aa00b --- /dev/null +++ b/lib/helper_functions/newDbUrl.js @@ -0,0 +1,5 @@ +module.exports = (orgId) => { + // assuming local dbs for now. + // to do add docker and hosted dbs. + return `mongodb://localhost:27017/${orgId}?retryWrites=true&w=majority`; +}; diff --git a/lib/helper_functions/orgHasTenant.js b/lib/helper_functions/orgHasTenant.js new file mode 100644 index 0000000000..a3c65aa285 --- /dev/null +++ b/lib/helper_functions/orgHasTenant.js @@ -0,0 +1,6 @@ +const Tenant = require('../models/Tenant'); + +module.exports = async (orgId) => { + const tenant = await Tenant.find({ organization: orgId }); + return tenant; +}; diff --git a/lib/helper_functions/tenantCtx.js b/lib/helper_functions/tenantCtx.js new file mode 100644 index 0000000000..84d704ddb8 --- /dev/null +++ b/lib/helper_functions/tenantCtx.js @@ -0,0 +1,17 @@ +const MainDB = require('../models/'); +const getTenantFromId = require('./getTenantFromId'); +const { getTenantConnection } = require('../ConnectionManager'); + +module.exports = async (mergedId) => { + const { tenantId, id } = getTenantFromId(mergedId); + if (!tenantId) + return { + db: MainDB, + id: mergedId, + tenantId: null, + }; + else { + const db = await getTenantConnection(tenantId); + return { db, id, tenantId }; + } +}; diff --git a/lib/models/Organization.js b/lib/models/Organization.js index 0fad4dd5a5..9501fbdd1b 100644 --- a/lib/models/Organization.js +++ b/lib/models/Organization.js @@ -74,6 +74,11 @@ const organizationSchema = new Schema({ ], visibleInSearch: Boolean, tags: [], + functionality: { + type: [String], + required: true, + default: ['DirectChat', 'Posts', 'Events', 'Groups', 'GroupChats'], + }, createdAt: { type: Date, default: Date.now, diff --git a/lib/models/Post.js b/lib/models/Post.js index 2ef30d1693..099babd09c 100644 --- a/lib/models/Post.js +++ b/lib/models/Post.js @@ -1,6 +1,7 @@ /* eslint-disable prettier/prettier */ const mongoose = require('mongoose'); const mongoosePaginate = require('mongoose-paginate-v2'); +const User = require('./User'); const Schema = mongoose.Schema; @@ -32,7 +33,7 @@ const postSchema = new Schema({ }, creator: { type: Schema.Types.ObjectId, - ref: 'User', + ref: User, required: true, }, organization: { diff --git a/lib/models/Tenant.js b/lib/models/Tenant.js new file mode 100644 index 0000000000..13d2994fd3 --- /dev/null +++ b/lib/models/Tenant.js @@ -0,0 +1,36 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const tenantSchema = new Schema({ + organization: { + type: Schema.Types.ObjectId, + ref: 'Organizaiton', + required: true, + }, + url: { + type: String, + required: true, + }, + scheme: { + type: [String], + default: [], + }, + 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); diff --git a/lib/models/User.js b/lib/models/User.js index 161f3d8e96..bc93f5773b 100644 --- a/lib/models/User.js +++ b/lib/models/User.js @@ -43,9 +43,10 @@ const userSchema = new Schema({ ref: 'Organization', }, ], + createdTasks: [{ type: String, ref: 'Task' }], createdEvents: [ { - type: Schema.Types.ObjectId, + type: String, ref: 'Event', }, ], @@ -55,6 +56,12 @@ const userSchema = new Schema({ default: 'USER', required: true, }, + directChats: [ + { + type: String, + ref: 'DirectChat', + }, + ], joinedOrganizations: [ { type: Schema.Types.ObjectId, @@ -63,13 +70,13 @@ const userSchema = new Schema({ ], registeredEvents: [ { - type: Schema.Types.ObjectId, + type: String, ref: 'Event', }, ], eventAdmin: [ { - type: Schema.Types.ObjectId, + type: String, ref: 'Event', }, ], diff --git a/lib/models/index.js b/lib/models/index.js new file mode 100644 index 0000000000..252a229278 --- /dev/null +++ b/lib/models/index.js @@ -0,0 +1,47 @@ +const Chat = require('./Chat'); +const Comment = require('./Comment'); +const DirectChat = require('./DirectChat'); +const DirectChatMessage = require('./DirectChatMessage'); +const Event = require('./Event'); +const EventProject = require('./EventProject'); +const File = require('./File'); +const Group = require('./Group'); +const GroupChat = require('./GroupChat'); +const GroupChatMessage = require('./GroupChatMessage'); +const ImageHash = require('./ImageHash'); +const Language = require('./Language'); +const MembershipRequest = require('./MembershipRequest'); +const Message = require('./Message'); +const Organization = require('./Organization'); +const Plugin = require('./Plugin'); +const PluginsField = require('./PluginsField'); +const Post = require('./Post'); +const Task = require('./Task'); +const Tenant = require('./Tenant'); +const User = require('./User'); +const UserAttendes = require('./UserAttendes'); + +module.exports = { + Chat, + Comment, + DirectChat, + DirectChatMessage, + Event, + EventProject, + File, + Group, + GroupChat, + GroupChatMessage, + ImageHash, + Language, + MembershipRequest, + Message, + Organization, + Plugin, + PluginsField, + Post, + Task, + Tenant, + User, + UserAttendes, +}; diff --git a/lib/resolvers/GroupChatMessage.js b/lib/resolvers/GroupChatMessage.js index baed6b4baa..653514f10f 100644 --- a/lib/resolvers/GroupChatMessage.js +++ b/lib/resolvers/GroupChatMessage.js @@ -1,9 +1,13 @@ const User = require('../models/User'); -const GroupChat = require('../models/GroupChat'); +const { tenantCtx } = require('../helper_functions'); module.exports = { groupChatMessageBelongsTo: async (parent) => { - return await GroupChat.findById(parent.groupChatMessageBelongsTo); + const { id: chatId, db } = await tenantCtx( + parent.groupChatMessageBelongsTo + ); + const { GroupChat } = db; + return await GroupChat.findById(chatId); }, sender: async (parent) => { return await User.findById(parent.sender); diff --git a/lib/resolvers/Organization.js b/lib/resolvers/Organization.js index 79b90b9e57..de4c74adf5 100644 --- a/lib/resolvers/Organization.js +++ b/lib/resolvers/Organization.js @@ -4,6 +4,15 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const Organization = { + functionality: (parent) => { + return { + DirectChat: parent.functionality.includes('DirectChat'), + Posts: parent.functionality.includes('Posts'), + Events: parent.functionality.includes('Events'), + Groups: parent.functionality.includes('Groups'), + GroupChats: parent.functionality.includes('GroupChats'), + }; + }, creator: async (parent) => { const user = await User.findById(parent.creator._id); if (!user) { diff --git a/lib/resolvers/admin_mutations/admin-remove-event.js b/lib/resolvers/admin_mutations/admin-remove-event.js index 433bd046aa..5825dea1fd 100644 --- a/lib/resolvers/admin_mutations/admin-remove-event.js +++ b/lib/resolvers/admin_mutations/admin-remove-event.js @@ -1,13 +1,15 @@ const User = require('../../models/User'); const Organization = require('../../models/Organization'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); const adminCheck = require('../functions/adminCheck'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); module.exports = async (parent, args, context) => { + const { id: eventId, db } = await tenantCtx(args.eventId); //find event - let event = await Event.findOne({ _id: args.eventId }); + const { Event } = db; + let event = await Event.findOne({ _id: eventId }); if (!event) { throw new NotFoundError( process.env.NODE_ENV !== 'production' @@ -49,21 +51,22 @@ module.exports = async (parent, args, context) => { user.overwrite({ ...user._doc, eventAdmin: user._doc.eventAdmin.filter( - (eventAdmin) => eventAdmin !== event.id + (eventAdmin) => eventAdmin !== args.eventId ), createdEvents: user._doc.createdEvents.filter( - (createdEvent) => createdEvent !== event.id + (createdEvent) => createdEvent !== args.eventId ), registeredEvents: user._doc.registeredEvents.filter( - (registeredEvent) => registeredEvent !== event.id + (registeredEvent) => registeredEvent !== args.eventId ), }); await user.save(); //delete post - await Event.deleteOne({ _id: args.eventId }); + await Event.deleteOne({ _id: eventId }); + event._doc._id = args.eventId; //return user return { ...event._doc, diff --git a/lib/resolvers/admin_mutations/admin-remove-group-chat.js b/lib/resolvers/admin_mutations/admin-remove-group-chat.js index b1386df8e4..7d429f06e5 100644 --- a/lib/resolvers/admin_mutations/admin-remove-group-chat.js +++ b/lib/resolvers/admin_mutations/admin-remove-group-chat.js @@ -4,7 +4,7 @@ const adminCheck = require('../functions/adminCheck'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); -const GroupChat = require('../../models/GroupChat'); +const { tenantCtx } = require('../../helper_functions'); const { IN_PRODUCTION, USER_NOT_FOUND, @@ -22,8 +22,10 @@ const { } = require('../../../constants'); module.exports = async (parent, args, context) => { + const { id: groupId, db } = await tenantCtx(args.groupId); + const { GroupChat } = db; //find message - let group = await GroupChat.findOne({ _id: args.groupId }); + let group = await GroupChat.findOne({ _id: groupId }); if (!group) { throw new NotFoundError( @@ -77,9 +79,10 @@ module.exports = async (parent, args, context) => { // await user.save(); //delete post - await GroupChat.deleteOne({ _id: args.groupId }); + await GroupChat.deleteOne({ _id: groupId }); //return user + group._doc._id = args.groupId; return { ...group._doc, }; diff --git a/lib/resolvers/admin_mutations/admin-remove-post.js b/lib/resolvers/admin_mutations/admin-remove-post.js index 8ce1a3c8e4..481c7c55d0 100644 --- a/lib/resolvers/admin_mutations/admin-remove-post.js +++ b/lib/resolvers/admin_mutations/admin-remove-post.js @@ -1,6 +1,6 @@ +const { tenantCtx, addTenantId } = require('../../helper_functions'); const User = require('../../models/User'); const Organization = require('../../models/Organization'); -const Post = require('../../models/Post'); const adminCheck = require('../functions/adminCheck'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -17,7 +17,8 @@ module.exports = async (parent, args, context) => { 'organization' ); } - + const { id, db, tenantId } = await tenantCtx(args.postId); + const { Post } = db; //gets user in token - to be used later on let user = await User.findOne({ _id: context.userId }); if (!user) { @@ -34,7 +35,7 @@ module.exports = async (parent, args, context) => { adminCheck(context, org); //find post - let post = await Post.findOne({ _id: args.postId }); + let post = await Post.findOne({ _id: id }); if (!post) { throw new NotFoundError( process.env.NODE_ENV !== 'production' @@ -48,20 +49,13 @@ module.exports = async (parent, args, context) => { //remove post from organization org.overwrite({ ...org._doc, - posts: org._doc.posts.filter((post) => post !== args.postId), + posts: org._doc.posts.filter((post) => post !== id), }); await org.save(); - // //remove post from user - // user.overwrite({ - // ...user._doc, - // posts: user._doc.posts.filter((post) => post != args.postId), - // }); - // await user.save(); - //delete post - await Post.deleteOne({ _id: args.postId }); - + await Post.deleteOne({ _id: id }); + post._doc._id = addTenantId(id, tenantId); //return user return post._doc; }; diff --git a/lib/resolvers/auth_mutations/login.js b/lib/resolvers/auth_mutations/login.js index c220a7678d..5ed23f9f17 100644 --- a/lib/resolvers/auth_mutations/login.js +++ b/lib/resolvers/auth_mutations/login.js @@ -1,4 +1,5 @@ const bcrypt = require('bcryptjs'); +const { customArrayPopulate } = require('../user_query/users'); const User = require('../../models/User'); const { createAccessToken, @@ -16,13 +17,10 @@ module.exports = async (parent, args) => { const user = await User.findOne({ email: args.data.email.toLowerCase() }) .populate('joinedOrganizations') .populate('createdOrganizations') - .populate('createdEvents') - .populate('registeredEvents') - .populate('eventAdmin') .populate('adminFor') - .populate('membershipRequests') .populate('organizationsBlockedBy') - .populate('organizationUserBelongsTo'); + .populate('organizationUserBelongsTo') + .populate('membershipRequests'); if (!user) { throw new NotFoundError( @@ -32,6 +30,10 @@ module.exports = async (parent, args) => { ); } + await customArrayPopulate(user, 'eventAdmin', 'Event'); + await customArrayPopulate(user, 'createdEvents', 'Event'); + await customArrayPopulate(user, 'registeredEvents', 'Event'); + const isEqual = await bcrypt.compare(args.data.password, user._doc.password); if (!isEqual) { diff --git a/lib/resolvers/direct_chat_mutations/createDirectChat.js b/lib/resolvers/direct_chat_mutations/createDirectChat.js index 74ce73e41f..cac1776976 100644 --- a/lib/resolvers/direct_chat_mutations/createDirectChat.js +++ b/lib/resolvers/direct_chat_mutations/createDirectChat.js @@ -1,6 +1,7 @@ const User = require('../../models/User'); -const DirectChat = require('../../models/DirectChat'); const Organization = require('../../models/Organization'); +const { getTenantConnection } = require('../../ConnectionManager'); +const { addTenantId } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -44,13 +45,33 @@ module.exports = async (parent, args, context) => { usersInChat.push(user); } + const { DirectChat } = await getTenantConnection(args.data.organizationId); let directChat = new DirectChat({ creator: user, users: usersInChat, organization: org, }); - directChat = await directChat.save(); + for (let userId of args.data.userIds) { + await User.updateOne( + { + _id: userId, + }, + { + $push: { + directChats: addTenantId( + directChat._doc._id, + args.data.organizationId + ), + }, + } + ); + } + directChat = await directChat.save(); + directChat._doc._id = addTenantId( + directChat._doc._id, + args.data.organizationId + ); return directChat.toObject(); }; diff --git a/lib/resolvers/direct_chat_mutations/removeDirectChat.js b/lib/resolvers/direct_chat_mutations/removeDirectChat.js index 45703a8a9e..b0a4cfe1e7 100644 --- a/lib/resolvers/direct_chat_mutations/removeDirectChat.js +++ b/lib/resolvers/direct_chat_mutations/removeDirectChat.js @@ -1,5 +1,5 @@ -const DirectChat = require('../../models/DirectChat'); -const DirectChatMessage = require('../../models/DirectChatMessage'); +const User = require('../../models/User'); +const { tenantCtx } = require('../../helper_functions'); const adminCheck = require('../functions/adminCheck'); const organizationExists = require('../../helper_functions/organizationExists'); const { NotFoundError } = require('errors'); @@ -10,7 +10,9 @@ const requestContext = require('talawa-request-context'); module.exports = async (parent, args, context) => { const org = await organizationExists(args.organizationId); - const chat = await DirectChat.findById(args.chatId); + const { db, id: chatId } = await tenantCtx(args.chatId); + const { DirectChat, DirectChatMessage } = db; + const chat = await DirectChat.findById(chatId); if (!chat) { throw new NotFoundError( requestContext.translate('chat.notFound'), @@ -27,8 +29,19 @@ module.exports = async (parent, args, context) => { $in: [...chat.messages], }, }); + await User.updateMany( + { + directChats: chatId, + }, + { + $pull: { + directChats: args.chatId, + }, + } + ); - await DirectChat.deleteOne({ _id: args.chatId }); + await DirectChat.deleteOne({ _id: chatId }); - return chat; + chat._doc._id = args.chatId; + return chat._doc; }; diff --git a/lib/resolvers/direct_chat_mutations/sendMessageToDirectChat.js b/lib/resolvers/direct_chat_mutations/sendMessageToDirectChat.js index c5cfef4bb8..3fbbf81c1d 100644 --- a/lib/resolvers/direct_chat_mutations/sendMessageToDirectChat.js +++ b/lib/resolvers/direct_chat_mutations/sendMessageToDirectChat.js @@ -1,5 +1,6 @@ -const DirectChat = require('../../models/DirectChat'); -const DirectChatMessage = require('../../models/DirectChatMessage'); +// const DirectChat = require('../../models/DirectChat'); +// const DirectChatMessage = require('../../models/DirectChatMessage'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); const userExists = require('../../helper_functions/userExists'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -11,7 +12,10 @@ const { CHAT_NOT_FOUND_PARAM, } = require('../../../constants'); module.exports = async (parent, args, context) => { - const chat = await DirectChat.findById(args.chatId); + const { db, id: chatId, tenantId } = await tenantCtx(args.chatId); + + const { DirectChat, DirectChatMessage } = db; + const chat = await DirectChat.findById(chatId); if (!chat) { throw new NotFoundError( !IN_PRODUCTION @@ -39,7 +43,7 @@ module.exports = async (parent, args, context) => { // add message to chat await DirectChat.updateOne( { - _id: args.chatId, + _id: chatId, }, { $set: { @@ -48,6 +52,7 @@ module.exports = async (parent, args, context) => { } ); + message._doc._id = addTenantId(message._doc._id, tenantId); //calls subscription context.pubsub.publish('MESSAGE_SENT_TO_DIRECT_CHAT', { messageSentToDirectChat: message._doc, diff --git a/lib/resolvers/direct_chat_query/directChatMessages.js b/lib/resolvers/direct_chat_query/directChatMessages.js index 71be8477f1..e490cc81f1 100644 --- a/lib/resolvers/direct_chat_query/directChatMessages.js +++ b/lib/resolvers/direct_chat_query/directChatMessages.js @@ -1,5 +1,16 @@ -const DirectChatMessages = require('../../models/DirectChatMessage'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async () => { - return await DirectChatMessages.find(); + const directChatMessages = []; + const connections = getAllConnections(); + for (let conn in connections) { + const { DirectChatMessage } = connections[conn]; + const curMessages = await DirectChatMessage.find(); + for (let message of curMessages) { + message._doc._id = addTenantId(message._doc._id, conn); + directChatMessages.push(message); + } + } + return directChatMessages; }; diff --git a/lib/resolvers/direct_chat_query/directChats.js b/lib/resolvers/direct_chat_query/directChats.js index 893eeb4b93..fc0abc8aab 100644 --- a/lib/resolvers/direct_chat_query/directChats.js +++ b/lib/resolvers/direct_chat_query/directChats.js @@ -1,5 +1,16 @@ -const DirectChat = require('../../models/DirectChat'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async () => { - return await DirectChat.find(); + const directChats = []; + const connections = getAllConnections(); + for (let conn in connections) { + const { DirectChat } = connections[conn]; + const curChats = await DirectChat.find(); + for (let chat of curChats) { + chat._doc._id = addTenantId(chat._doc._id, conn); + directChats.push(chat); + } + } + return directChats; }; diff --git a/lib/resolvers/direct_chat_query/directChatsByUserID.js b/lib/resolvers/direct_chat_query/directChatsByUserID.js index 283f4aecda..c04ae9b8e2 100644 --- a/lib/resolvers/direct_chat_query/directChatsByUserID.js +++ b/lib/resolvers/direct_chat_query/directChatsByUserID.js @@ -2,11 +2,34 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); -const DirectChat = require('../../models/DirectChat'); +const User = require('../../models/User'); module.exports = async (parent, args) => { - const directChatsFound = await DirectChat.find({ users: args.id }); + const user = await User.findOne({ _id: args.id }); + if (!user) { + throw new NotFoundError( + process.env.NODE_ENV !== 'production' + ? 'DirectChats not found' + : requestContext.translate('directChats.notFound'), + 'directChats.notFound', + 'directChats' + ); + } + const directChatsFound = []; + const orgs = {}; + for (let i = 0; i < user.directChats.length; i++) { + const { tenantId, db } = await tenantCtx(user.directChats[i]); + if (tenantId in orgs) continue; + orgs[tenantId] = true; + const { DirectChat } = db; + const curChats = await DirectChat.find({ users: args.id }); + for (let i = 0; i < curChats.length; i++) { + curChats[i]._doc._id = addTenantId(curChats[i]._doc._id, tenantId); + directChatsFound.push(curChats[i]); + } + } if (directChatsFound.length === 0) { throw new NotFoundError( diff --git a/lib/resolvers/direct_chat_query/directChatsMessagesByChatID.js b/lib/resolvers/direct_chat_query/directChatsMessagesByChatID.js index 993221b285..08f2d9f7a8 100644 --- a/lib/resolvers/direct_chat_query/directChatsMessagesByChatID.js +++ b/lib/resolvers/direct_chat_query/directChatsMessagesByChatID.js @@ -2,7 +2,8 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); -const DirectChatMessages = require('../../models/DirectChatMessage'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); + const { IN_PRODUCTION, CHAT_NOT_FOUND, @@ -12,8 +13,10 @@ const { } = require('../../../constants'); module.exports = async (parent, args) => { - const directChatsMessagesFound = await DirectChatMessages.find({ - directChatMessageBelongsTo: args.id, + const { db, tenantId, id: chatId } = await tenantCtx(args.id); + const { DirectChatMessage } = db; + const directChatsMessagesFound = await DirectChatMessage.find({ + directChatMessageBelongsTo: chatId, }); if (directChatsMessagesFound.length === 0) { throw new NotFoundError( @@ -24,5 +27,8 @@ module.exports = async (parent, args) => { CHAT_NOT_FOUND_PARAM ); } + for (let message of directChatsMessagesFound) { + message._doc._id = addTenantId(message._doc._id, tenantId); + } return directChatsMessagesFound; }; diff --git a/lib/resolvers/event_mutations/createEvent.js b/lib/resolvers/event_mutations/createEvent.js index 432bc800c8..d05c2435ab 100644 --- a/lib/resolvers/event_mutations/createEvent.js +++ b/lib/resolvers/event_mutations/createEvent.js @@ -1,6 +1,7 @@ const { NotFoundError, UnauthorizedError } = require('errors'); +const { addTenantId } = require('../../helper_functions/'); +const { getTenantConnection } = require('../../../lib/ConnectionManager'); const User = require('../../models/User'); -const Event = require('../../models/Event'); const Organization = require('../../models/Organization'); const requestContext = require('talawa-request-context'); const { @@ -24,6 +25,8 @@ var { applicationDefault } = require('firebase-admin').credential; admin.initializeApp({ credential: applicationDefault() }); const createEvent = async (parent, args, context) => { + const db = await getTenantConnection(args.data.organizationId); + const { Event } = db; const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -90,9 +93,9 @@ const createEvent = async (parent, args, context) => { { _id: user.id }, { $push: { - eventAdmin: newEvent, - createdEvents: newEvent, - registeredEvents: newEvent, + eventAdmin: addTenantId(newEvent._id, args.data.organizationId), + createdEvents: addTenantId(newEvent._id, args.data.organizationId), + registeredEvents: addTenantId(newEvent._id, args.data.organizationId), }, } ); @@ -111,7 +114,7 @@ const createEvent = async (parent, args, context) => { }); } } - + newEvent._doc._id = addTenantId(newEvent._id, args.data.organizationId); return { ...newEvent._doc, }; diff --git a/lib/resolvers/event_mutations/registerForEvent.js b/lib/resolvers/event_mutations/registerForEvent.js index c622c000f1..1bbf28b533 100644 --- a/lib/resolvers/event_mutations/registerForEvent.js +++ b/lib/resolvers/event_mutations/registerForEvent.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const { @@ -18,6 +18,8 @@ const { REGISTRANT_ALREADY_EXIST_PARAM, } = require('../../../constants'); const registerForEvent = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { Event } = db; const userFound = await User.findOne({ _id: context.userId }); if (!userFound) { throw new NotFoundError( @@ -29,7 +31,7 @@ const registerForEvent = async (parent, args, context) => { ); } - const eventFound = await Event.findOne({ _id: args.id }); + const eventFound = await Event.findOne({ _id: id }); if (!eventFound) { throw new NotFoundError( !IN_PRODUCTION @@ -67,7 +69,7 @@ const registerForEvent = async (parent, args, context) => { }, { $push: { - registeredEvents: eventFound, + registeredEvents: args.id, }, } ); @@ -77,7 +79,7 @@ const registerForEvent = async (parent, args, context) => { if (!isAlreadyExists) { newEvent = await Event.findOneAndUpdate( { - _id: args.id, + _id: id, status: 'ACTIVE', }, { @@ -104,7 +106,7 @@ const registerForEvent = async (parent, args, context) => { newEvent = await Event.findOneAndUpdate( { - _id: args.id, + _id: id, status: 'ACTIVE', }, { @@ -117,7 +119,7 @@ const registerForEvent = async (parent, args, context) => { } ); } - + newEvent._doc._id = args.id; return newEvent; }; diff --git a/lib/resolvers/event_mutations/removeEvent.js b/lib/resolvers/event_mutations/removeEvent.js index 204eb993f2..03e828d117 100644 --- a/lib/resolvers/event_mutations/removeEvent.js +++ b/lib/resolvers/event_mutations/removeEvent.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions/'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const { @@ -19,6 +19,7 @@ const { } = require('../../../constants'); const removeEvent = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -30,7 +31,8 @@ const removeEvent = async (parent, args, context) => { ); } - const event = await Event.findOne({ _id: args.id }); + const { Event } = db; + const event = await Event.findOne({ _id: id }); if (!event) { throw new NotFoundError( !IN_PRODUCTION @@ -58,7 +60,7 @@ const removeEvent = async (parent, args, context) => { } await User.updateMany( - { createdEvents: args.id }, + { createdEvents: id }, { $pull: { createdEvents: args.id, @@ -67,7 +69,7 @@ const removeEvent = async (parent, args, context) => { ); await User.updateMany( - { eventAdmin: args.id }, + { eventAdmin: id }, { $pull: { eventAdmin: args.id, @@ -75,7 +77,8 @@ const removeEvent = async (parent, args, context) => { } ); - await Event.findOneAndUpdate({ _id: args.id }, { status: 'DELETED' }); + await Event.findOneAndUpdate({ _id: id }, { status: 'DELETED' }); + event._doc._id = args.id; return event; }; diff --git a/lib/resolvers/event_mutations/unregisterForEvent.js b/lib/resolvers/event_mutations/unregisterForEvent.js index 8b166ca5d1..c2c7d68026 100644 --- a/lib/resolvers/event_mutations/unregisterForEvent.js +++ b/lib/resolvers/event_mutations/unregisterForEvent.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const { @@ -19,6 +19,8 @@ const { } = require('../../../constants'); const unregisterForEventByUser = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { Event } = db; const userFound = await User.findOne({ _id: context.userId, }); @@ -32,9 +34,8 @@ const unregisterForEventByUser = async (parent, args, context) => { USER_NOT_FOUND_PARAM ); } - const eventFound = await Event.findOne({ - _id: args.id, + _id: id, }); if (!eventFound) { @@ -73,7 +74,7 @@ const unregisterForEventByUser = async (parent, args, context) => { const newEvent = await Event.findOneAndUpdate( { - _id: args.id, + _id: id, status: 'ACTIVE', }, { @@ -86,6 +87,7 @@ const unregisterForEventByUser = async (parent, args, context) => { } ); + newEvent._doc._id = args.id; return newEvent; } else { throw new NotFoundError( diff --git a/lib/resolvers/event_mutations/updateEvent.js b/lib/resolvers/event_mutations/updateEvent.js index 1724cfbafd..db4c052a85 100644 --- a/lib/resolvers/event_mutations/updateEvent.js +++ b/lib/resolvers/event_mutations/updateEvent.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const { @@ -19,6 +19,8 @@ const { } = require('../../../constants'); const updateEvent = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { Event } = db; const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -30,7 +32,7 @@ const updateEvent = async (parent, args, context) => { ); } - const event = await Event.findOne({ _id: args.id }); + const event = await Event.findOne({ _id: id }); if (!event) { throw new NotFoundError( !IN_PRODUCTION @@ -52,10 +54,11 @@ const updateEvent = async (parent, args, context) => { } const newEvent = await Event.findOneAndUpdate( - { _id: args.id }, + { _id: id }, { ...args.data }, { new: true } ); + newEvent._doc._id = args.id; return { ...newEvent._doc, }; diff --git a/lib/resolvers/event_project_mutations/createProject.js b/lib/resolvers/event_project_mutations/createProject.js index 40b974bf5b..c2eeaae1fc 100644 --- a/lib/resolvers/event_project_mutations/createProject.js +++ b/lib/resolvers/event_project_mutations/createProject.js @@ -1,6 +1,5 @@ const User = require('../../models/User'); -const EventProject = require('../../models/EventProject'); -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -16,8 +15,10 @@ const createEventProject = async (parent, args, context) => { 'user' ); } + const { id, db } = await tenantCtx(args.data.eventId); + const { EventProject, Event } = db; - const eventFound = await Event.findOne({ _id: args.data.eventId }); + const eventFound = await Event.findOne({ _id: id }); if (!eventFound) { throw new NotFoundError( process.env.NODE_ENV !== 'production' diff --git a/lib/resolvers/event_project_mutations/removeProject.js b/lib/resolvers/event_project_mutations/removeProject.js index b24f0ab379..9867255241 100644 --- a/lib/resolvers/event_project_mutations/removeProject.js +++ b/lib/resolvers/event_project_mutations/removeProject.js @@ -1,10 +1,12 @@ const User = require('../../models/User'); -const EventProject = require('../../models/EventProject'); +const { tenantCtx } = require('../../helper_functions'); const constants = require('../../../constants'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const removeEventProject = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { EventProject } = db; const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -16,7 +18,7 @@ const removeEventProject = async (parent, args, context) => { ); } - const eventProject = await EventProject.findOne({ _id: args.id }); + const eventProject = await EventProject.findOne({ _id: id }); if (!eventProject) { throw new NotFoundError( !constants.IN_PRODUCTION @@ -36,7 +38,8 @@ const removeEventProject = async (parent, args, context) => { ); } - await EventProject.deleteOne({ _id: args.id }); + await EventProject.deleteOne({ _id: id }); + eventProject._doc._id = args.id; return eventProject; }; diff --git a/lib/resolvers/event_project_mutations/updateProject.js b/lib/resolvers/event_project_mutations/updateProject.js index 5ee958f0bb..f77a37cbbd 100644 --- a/lib/resolvers/event_project_mutations/updateProject.js +++ b/lib/resolvers/event_project_mutations/updateProject.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const EventProject = require('../../models/EventProject'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -16,8 +16,10 @@ const updateEvent = async (parent, args, context) => { 'user' ); } + const { id, db } = await tenantCtx(args.id); + const { EventProject } = db; - const eventProject = await EventProject.findOne({ _id: args.id }); + const eventProject = await EventProject.findOne({ _id: id }); if (!eventProject) { throw new NotFoundError( @@ -41,11 +43,10 @@ const updateEvent = async (parent, args, context) => { } const newEventProject = await EventProject.findOneAndUpdate( - { _id: args.id }, + { _id: id }, { ...args.data }, { new: true } ); - return newEventProject; }; diff --git a/lib/resolvers/event_query/event.js b/lib/resolvers/event_query/event.js index a3bec4b59b..198a008045 100644 --- a/lib/resolvers/event_query/event.js +++ b/lib/resolvers/event_query/event.js @@ -1,16 +1,19 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); -const Event = require('../../models/Event'); +const User = require('../../models/User'); +const { tenantCtx } = require('../../helper_functions'); module.exports = async (parent, args) => { + const { id: eventId, db } = await tenantCtx(args.id); + const { Event, Task } = db; const eventFound = await Event.findOne({ - _id: args.id, + _id: eventId, status: 'ACTIVE', }) - .populate('creator', '-password') - .populate('tasks') - .populate('admins', '-password'); + .populate('creator', '-password', User) + .populate('tasks', '', Task) + .populate('admins', '-password', User); if (!eventFound) { throw new NotFoundError( @@ -19,6 +22,6 @@ module.exports = async (parent, args) => { 'event' ); } - + eventFound._doc._id = args.id; return eventFound; }; diff --git a/lib/resolvers/event_query/events.js b/lib/resolvers/event_query/events.js index 44ed89e6bc..f81343eaad 100644 --- a/lib/resolvers/event_query/events.js +++ b/lib/resolvers/event_query/events.js @@ -1,4 +1,6 @@ -const Event = require('../../models/Event'); +const User = require('../../models/User'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; @@ -49,11 +51,18 @@ module.exports = async (parent, args) => { } } - const eventsResponse = await Event.find({ status: 'ACTIVE' }) - .sort(sort) - .populate('creator', '-password') - .populate('tasks') - .populate('admins', '-password'); - + let eventsResponse = []; + const connections = getAllConnections(); + for (let conn in connections) { + const events = await connections[conn].Event.find({ status: 'ACTIVE' }) + .populate('creator', '-password', User) + .populate('tasks', '', connections[conn].Task) + .populate('admins', '-password', User) + .sort(sort); + for (let i = 0; i < events.length; i++) { + events[i]._doc._id = addTenantId(events[i]._id, conn); + eventsResponse.push(events[i]); + } + } return eventsResponse; }; diff --git a/lib/resolvers/event_query/eventsByOrganization.js b/lib/resolvers/event_query/eventsByOrganization.js index 27b0baf17b..7d9c976d1a 100644 --- a/lib/resolvers/event_query/eventsByOrganization.js +++ b/lib/resolvers/event_query/eventsByOrganization.js @@ -1,5 +1,7 @@ -const Event = require('../../models/Event'); +const User = require('../../models/User'); const { STATUS_ACTIVE } = require('../../../constants'); +const { getTenantConnection } = require('../../ConnectionManager'); +const { addTenantId } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; @@ -50,16 +52,19 @@ module.exports = async (parent, args) => { } } + const db = await getTenantConnection(args.id); + const { Event, Task } = db; const eventResponse = await Event.find({ organization: args.id, status: 'ACTIVE', }) .sort(sort) - .populate('creator', '-password') - .populate('tasks') - .populate('admins', '-password'); + .populate('creator', '-password', User) + .populate('tasks', '', Task) + .populate('admins', '-password', User); eventResponse.forEach((event) => { + event._doc._id = addTenantId(event._doc._id, args.id); event.registrants = event.registrants.filter( (registrant) => registrant.status === STATUS_ACTIVE ); diff --git a/lib/resolvers/event_query/isUserRegister.js b/lib/resolvers/event_query/isUserRegister.js index adcea00232..3e3e381755 100644 --- a/lib/resolvers/event_query/isUserRegister.js +++ b/lib/resolvers/event_query/isUserRegister.js @@ -7,16 +7,19 @@ const { EVENT_NOT_FOUND_MESSAGE, EVENT_NOT_FOUND_PARAM, } = require('../../../constants'); -const Event = require('../../models/Event'); +const User = require('../../models/User'); +const { tenantCtx } = require('../../helper_functions'); module.exports = async (parent, args, context) => { + const { id: eventId, db } = await tenantCtx(args.eventId); + const { Event, Task } = db; const eventFound = await Event.findOne({ - _id: args.eventId, + _id: eventId, status: 'ACTIVE', }) - .populate('creator', '-password') - .populate('tasks') - .populate('admins', '-password'); + .populate('creator', '-password', User) + .populate('tasks', '', Task) + .populate('admins', '-password', User); if (!eventFound) { throw new NotFoundError( @@ -39,6 +42,7 @@ module.exports = async (parent, args, context) => { } } + eventFound._doc._id = args.eventId; return { event: eventFound, isRegistered: isRegistered, diff --git a/lib/resolvers/event_query/registeredEventsByUser.js b/lib/resolvers/event_query/registeredEventsByUser.js index 95800b3965..c3d1a8f100 100644 --- a/lib/resolvers/event_query/registeredEventsByUser.js +++ b/lib/resolvers/event_query/registeredEventsByUser.js @@ -1,4 +1,5 @@ -const Event = require('../../models/Event'); +const User = require('../../models/User'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; @@ -49,17 +50,31 @@ module.exports = async (parent, args) => { } } - return await Event.find({ - status: 'ACTIVE', - registrants: { - $elemMatch: { - userId: args.id, - status: 'ACTIVE', + const user = await User.findById(args.id); + let events = []; + const orgs = {}; + for (let i = 0; i < user.registeredEvents.length; i++) { + const { tenantId, db } = await tenantCtx(user.registeredEvents[i]); + if (tenantId in orgs) continue; + orgs[tenantId] = true; + const { Event, Task } = db; + const curEvents = await Event.find({ + status: 'ACTIVE', + registrants: { + $elemMatch: { + userId: args.id, + status: 'ACTIVE', + }, }, - }, - }) - .sort(sort) - .populate('creator', '-password') - .populate('tasks') - .populate('admins', '-password'); + }) + .sort(sort) + .populate('creator', '-password', User) + .populate('tasks', '', Task) + .populate('admins', '-password', User); + for (let i = 0; i < curEvents.length; i++) { + curEvents[i]._doc._id = addTenantId(curEvents[i]._doc._id, tenantId); + events.push(curEvents[i]); + } + } + return events; }; diff --git a/lib/resolvers/event_query/registrantsByEvent.js b/lib/resolvers/event_query/registrantsByEvent.js index 3a303207a7..13f945b4d9 100644 --- a/lib/resolvers/event_query/registrantsByEvent.js +++ b/lib/resolvers/event_query/registrantsByEvent.js @@ -1,7 +1,7 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); - -const Event = require('../../models/Event'); +const { tenantCtx } = require('../../helper_functions'); +const User = require('../../models/User'); const { EVENT_NOT_FOUND, EVENT_NOT_FOUND_MESSAGE, @@ -11,10 +11,12 @@ const { } = require('../../../constants'); module.exports = async (parent, args) => { + const { id, db } = await tenantCtx(args.id); + const { Event } = db; const eventFound = await Event.findOne({ - _id: args.id, + _id: id, status: 'ACTIVE', - }).populate('registrants.user'); + }).populate('registrants.user', '', User); if (!eventFound) { throw new NotFoundError( diff --git a/lib/resolvers/event_query/tasksByEvent.js b/lib/resolvers/event_query/tasksByEvent.js index ba95ce68db..a57353e0d5 100644 --- a/lib/resolvers/event_query/tasksByEvent.js +++ b/lib/resolvers/event_query/tasksByEvent.js @@ -1,4 +1,5 @@ -const Task = require('../../models/Task'); +const User = require('../../models/Task'); +const { tenantCtx } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; @@ -28,9 +29,11 @@ module.exports = async (parent, args) => { sort = { deadline: -1 }; } } + const { id, db } = await tenantCtx(args.id); + const { Task } = db; - return await Task.find({ event: args.id }) + return await Task.find({ event: id }) .sort(sort) .populate('event') - .populate('creator', '-password'); + .populate('creator', '-password', User); }; diff --git a/lib/resolvers/group_chat_mutations/addUserToGroupChat.js b/lib/resolvers/group_chat_mutations/addUserToGroupChat.js index f57ea479e9..3e1595d4c4 100644 --- a/lib/resolvers/group_chat_mutations/addUserToGroupChat.js +++ b/lib/resolvers/group_chat_mutations/addUserToGroupChat.js @@ -1,5 +1,5 @@ const User = require('../../models/User'); -const GroupChat = require('../../models/GroupChat'); +const { tenantCtx } = require('../../helper_functions'); const adminCheck = require('../functions/adminCheck'); const organizationExists = require('../../helper_functions/organizationExists'); const { NotFoundError, ConflictError } = require('errors'); @@ -17,7 +17,9 @@ const { } = require('../../../constants'); module.exports = async (parent, args, context) => { - let chat = await GroupChat.findById(args.chatId); + const { id: chatId, db } = await tenantCtx(args.chatId); + const { GroupChat } = db; + let chat = await GroupChat.findById(chatId); if (!chat) { throw new NotFoundError( !IN_PRODUCTION @@ -48,8 +50,8 @@ module.exports = async (parent, args, context) => { ); } - return await GroupChat.findOneAndUpdate( - { _id: args.chatId }, + const groupChat = await GroupChat.findOneAndUpdate( + { _id: chatId }, { $set: { users: [...chat._doc.users, userBeingAdded], @@ -59,4 +61,7 @@ module.exports = async (parent, args, context) => { new: true, } ); + + groupChat._doc._id = args.chatId; + return groupChat._doc; }; diff --git a/lib/resolvers/group_chat_mutations/createGroupChat.js b/lib/resolvers/group_chat_mutations/createGroupChat.js index 7e2b083145..8575d4c3d2 100644 --- a/lib/resolvers/group_chat_mutations/createGroupChat.js +++ b/lib/resolvers/group_chat_mutations/createGroupChat.js @@ -1,5 +1,6 @@ const User = require('../../models/User'); -const GroupChat = require('../../models/GroupChat'); +const { getTenantConnection } = require('../../ConnectionManager'); +const { addTenantId } = require('../../helper_functions'); const Organization = require('../../models/Organization'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -38,6 +39,7 @@ module.exports = async (parent, args, context) => { usersInChat.push(user); } + const { GroupChat } = await getTenantConnection(args.data.organizationId); let groupChat = new GroupChat({ creator: userFound, users: usersInChat, @@ -46,6 +48,9 @@ module.exports = async (parent, args, context) => { }); groupChat = await groupChat.save(); - + groupChat._doc._id = addTenantId( + groupChat._doc._id, + args.data.organizationId + ); return groupChat._doc; }; diff --git a/lib/resolvers/group_chat_mutations/removeGroupChat.js b/lib/resolvers/group_chat_mutations/removeGroupChat.js index 8ccd433fce..d26793e738 100644 --- a/lib/resolvers/group_chat_mutations/removeGroupChat.js +++ b/lib/resolvers/group_chat_mutations/removeGroupChat.js @@ -1,14 +1,15 @@ -const GroupChat = require('../../models/GroupChat'); -const GroupChatMessage = require('../../models/GroupChatMessage'); const adminCheck = require('../functions/adminCheck'); const organizationExists = require('../../helper_functions/organizationExists'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); // admins of the organization can remove chats -- may change in the future module.exports = async (parent, args, context) => { - const chat = await GroupChat.findById(args.chatId); + const { id: chatId, db } = await tenantCtx(args.chatId); + const { GroupChat, GroupChatMessage } = db; + const chat = await GroupChat.findById(chatId); if (!chat) { throw new NotFoundError( requestContext.translate('chat.notFound'), @@ -28,7 +29,8 @@ module.exports = async (parent, args, context) => { }, }); - await GroupChat.deleteOne({ _id: args.chatId }); + await GroupChat.deleteOne({ _id: chatId }); + chat._doc._id = args.chatId; return chat; }; diff --git a/lib/resolvers/group_chat_mutations/removeUserFromGroupChat.js b/lib/resolvers/group_chat_mutations/removeUserFromGroupChat.js index db7cad301e..1f5fe69d85 100644 --- a/lib/resolvers/group_chat_mutations/removeUserFromGroupChat.js +++ b/lib/resolvers/group_chat_mutations/removeUserFromGroupChat.js @@ -1,11 +1,13 @@ -const GroupChat = require('../../models/GroupChat'); +const { tenantCtx } = require('../../helper_functions'); const adminCheck = require('../functions/adminCheck'); const organizationExists = require('../../helper_functions/organizationExists'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); module.exports = async (parent, args, context) => { - const chat = await GroupChat.findById(args.chatId); + const { id: chatId, db } = await tenantCtx(args.chatId); + const { GroupChat } = db; + const chat = await GroupChat.findById(chatId); if (!chat) { throw new NotFoundError( requestContext.translate('chat.notFound'), @@ -30,9 +32,9 @@ module.exports = async (parent, args, context) => { ); } - return await GroupChat.findOneAndUpdate( + const groupChat = await GroupChat.findOneAndUpdate( { - _id: args.chatId, + _id: chatId, }, { $set: { @@ -43,4 +45,6 @@ module.exports = async (parent, args, context) => { new: true, } ); + groupChat._doc._id = args.chatId; + return groupChat._doc; }; diff --git a/lib/resolvers/group_chat_mutations/sendMessageToGroupChat.js b/lib/resolvers/group_chat_mutations/sendMessageToGroupChat.js index dfbd251691..f02344065c 100644 --- a/lib/resolvers/group_chat_mutations/sendMessageToGroupChat.js +++ b/lib/resolvers/group_chat_mutations/sendMessageToGroupChat.js @@ -1,5 +1,4 @@ -const GroupChat = require('../../models/GroupChat'); -const GroupChatMessage = require('../../models/GroupChatMessage'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); const userExists = require('../../helper_functions/userExists'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -16,7 +15,9 @@ const { } = require('../../../constants'); module.exports = async (parent, args, context) => { - const chat = await GroupChat.findById(args.chatId); + const { db, id: chatId, tenantId } = await tenantCtx(args.chatId); + const { GroupChat, GroupChatMessage } = db; + const chat = await GroupChat.findById(chatId); if (!chat) { throw new NotFoundError( !IN_PRODUCTION @@ -44,7 +45,7 @@ module.exports = async (parent, args, context) => { } const message = new GroupChatMessage({ - groupChatMessageBelongsTo: chat._doc, + groupChatMessageBelongsTo: chatId, sender: sender._id, createdAt: new Date(), messageContent: args.messageContent, @@ -55,7 +56,7 @@ module.exports = async (parent, args, context) => { // add message to chat await GroupChat.updateOne( { - _id: args.chatId, + _id: chatId, }, { $set: { @@ -64,12 +65,12 @@ module.exports = async (parent, args, context) => { } ); + message._doc._id = addTenantId(message._doc._id, tenantId); //calls subscription context.pubsub.publish('MESSAGE_SENT_TO_GROUP_CHAT', { messageSentToGroupChat: { ...message._doc, }, }); - return message._doc; }; diff --git a/lib/resolvers/group_chat_query/groupChatMessages.js b/lib/resolvers/group_chat_query/groupChatMessages.js index ae73c079c1..e45f2183d0 100644 --- a/lib/resolvers/group_chat_query/groupChatMessages.js +++ b/lib/resolvers/group_chat_query/groupChatMessages.js @@ -1,5 +1,16 @@ -const GroupChatMessages = require('../../models/GroupChatMessage'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async () => { - return await GroupChatMessages.find(); + const groupChatMessages = []; + const connections = getAllConnections(); + for (let conn in connections) { + const { GroupChatMessage } = connections[conn]; + const curMessages = await GroupChatMessage.find(); + for (let message of curMessages) { + message._doc._id = addTenantId(message._doc._id, conn); + groupChatMessages.push(message); + } + } + return groupChatMessages; }; diff --git a/lib/resolvers/group_chat_query/groupChats.js b/lib/resolvers/group_chat_query/groupChats.js index d39b88f274..9eb687b902 100644 --- a/lib/resolvers/group_chat_query/groupChats.js +++ b/lib/resolvers/group_chat_query/groupChats.js @@ -1,5 +1,16 @@ -const GroupChat = require('../../models/GroupChat'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async () => { - return await GroupChat.find(); + const chats = []; + const connections = getAllConnections(); + for (let conn in connections) { + const { GroupChat } = connections[conn]; + const curChats = await GroupChat.find(); + for (let chat of curChats) { + chat._doc._id = addTenantId(chat._doc._id, conn); + chats.push(chat); + } + } + return chats; }; diff --git a/lib/resolvers/group_query/groups.js b/lib/resolvers/group_query/groups.js index 0fb898f6b8..e4c66f0477 100644 --- a/lib/resolvers/group_query/groups.js +++ b/lib/resolvers/group_query/groups.js @@ -1,5 +1,16 @@ -const Group = require('../../models/Group'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async () => { - return await Group.find(); + const groups = []; + const connections = getAllConnections(); + for (let conn in connections) { + const { Group } = connections[conn]; + const curGroups = await Group.find(); + for (let group of curGroups) { + group._doc._id = addTenantId(group._doc._id, conn); + groups.push(group); + } + } + return groups; }; diff --git a/lib/resolvers/organization_mutations/createOrganization.js b/lib/resolvers/organization_mutations/createOrganization.js index 5c68aa4b91..ee1205ab5a 100644 --- a/lib/resolvers/organization_mutations/createOrganization.js +++ b/lib/resolvers/organization_mutations/createOrganization.js @@ -1,12 +1,19 @@ const User = require('../../models/User'); const Organization = require('../../models/Organization'); +const Tenant = require('../../models/Tenant'); const userExists = require('../../helper_functions/userExists'); +const newDbUrl = require('../../helper_functions/newDbUrl'); +const { addTenantConnection } = require('../../ConnectionManager'); const uploadImage = require('../../helper_functions/uploadImage'); +const defaultScheme = ['DirectChat', 'Posts', 'Events', 'Groups', 'GroupChats']; const createOrganization = async (parent, args, context) => { //gets user in token - to be used later on let userFound = await userExists(context.userId); + // customize the schema + let schema = args.schema ? args.schema : []; + delete args.schema; //Upload file let uploadImageObj; @@ -14,6 +21,14 @@ const createOrganization = async (parent, args, context) => { uploadImageObj = await uploadImage(args.file, null); } + let functionality = []; + if (!args.data.customScheme) { + functionality = defaultScheme; + } else { + for (let f in args.data.functionality) { + if (args.data.functionality[f]) functionality.push(f); + } + } let newOrganization = new Organization({ ...args.data, image: uploadImageObj @@ -24,8 +39,18 @@ const createOrganization = async (parent, args, context) => { creator: userFound, admins: [userFound], members: [userFound], + functionality, + }); + + const savedOrg = await newOrganization.save(); + const url = newDbUrl(savedOrg._id.toString()); + const tenant = new Tenant({ + organization: savedOrg._id, + url, + scheme: schema, }); - await newOrganization.save(); + await tenant.save(); + await addTenantConnection(savedOrg._id); await User.findOneAndUpdate( { _id: userFound.id }, diff --git a/lib/resolvers/organization_mutations/removeOrganization.js b/lib/resolvers/organization_mutations/removeOrganization.js index c27a9dc04b..4fcc9cdc67 100644 --- a/lib/resolvers/organization_mutations/removeOrganization.js +++ b/lib/resolvers/organization_mutations/removeOrganization.js @@ -1,8 +1,14 @@ const User = require('../../models/User'); const MembershipRequest = require('../../models/MembershipRequest'); -const Comment = require('../../models/Comment'); -const Post = require('../../models/Post'); +const Tenant = require('../../models/Tenant'); +let Comment = require('../../models/Comment'); +let Post = require('../../models/Post'); const Organization = require('../../models/Organization'); +const { orgHasTenant } = require('../../helper_functions'); +const { + getTenantConnection, + destroyOneConnection, +} = require('../../ConnectionManager'); const creatorCheck = require('../functions/creatorCheck'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -47,6 +53,14 @@ const removeOrganizaiton = async (parent, args, context) => { // check if the user is the creator creatorCheck(context, org); + // check if has it's own tenant or just uses the main db. + const tenant = await orgHasTenant(args.id); + if (tenant) { + const conn = await getTenantConnection(args.id); + Post = conn.Post; + Comment = conn.Comment; + } + //remove posts related to this organization org.posts.forEach(async (postId) => { await Post.findByIdAndDelete(postId); @@ -101,6 +115,14 @@ const removeOrganizaiton = async (parent, args, context) => { await blocked.save(); } + // delete tenant data if exists. + if (tenant) { + const conn = await getTenantConnection(args.id); + if (conn) await conn.db.mongo.connection.db.dropDatabase(); + await Tenant.deleteOne({ organization: args.id }); + await destroyOneConnection(args.id); + } + // delete organzation await Organization.deleteOne({ _id: args.id }); diff --git a/lib/resolvers/post_mutations/createComment.js b/lib/resolvers/post_mutations/createComment.js index d730433c67..f896517373 100644 --- a/lib/resolvers/post_mutations/createComment.js +++ b/lib/resolvers/post_mutations/createComment.js @@ -1,11 +1,11 @@ const User = require('../../models/User'); -const Comment = require('../../models/Comment'); -const Post = require('../../models/Post'); +const { addTenantId, tenantCtx } = require('../../helper_functions/'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); module.exports = async (parent, args, context) => { + const { db, id: postId, tenantId } = await tenantCtx(args.postId); // gets user in token - to be used later on const user = await User.findOne({ _id: context.userId }); if (!user) { @@ -15,15 +15,16 @@ module.exports = async (parent, args, context) => { 'user' ); } + const { Post, Comment } = db; let newComment = new Comment({ ...args.data, creator: context.userId, - post: args.postId, + post: postId, }); await Post.updateOne( - { _id: args.postId }, + { _id: postId }, { $push: { comments: newComment, @@ -38,5 +39,7 @@ module.exports = async (parent, args, context) => { return { ...newComment._doc, + _id: addTenantId(newComment._id, tenantId), + post: addTenantId(newComment.post, tenantId), }; }; diff --git a/lib/resolvers/post_mutations/createPost.js b/lib/resolvers/post_mutations/createPost.js index b75b223d23..909fcfb8a5 100644 --- a/lib/resolvers/post_mutations/createPost.js +++ b/lib/resolvers/post_mutations/createPost.js @@ -1,7 +1,8 @@ -const User = require('../../models/User'); -const Post = require('../../models/Post'); const Organization = require('../../models/Organization'); +const User = require('../../models/User'); +const { getTenantConnection } = require('../../ConnectionManager/'); +const { addTenantId } = require('../../helper_functions/'); const uploadImage = require('../../helper_functions/uploadImage'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); @@ -27,6 +28,8 @@ module.exports = async (parent, args, context) => { 'organization' ); } + const tenant = await getTenantConnection(args.data.organizationId); + const { Post } = tenant; let uploadImageObj; if (args.file) { @@ -46,5 +49,6 @@ module.exports = async (parent, args, context) => { return { ...newPost._doc, + _id: addTenantId(newPost._id, args.data.organizationId), }; }; diff --git a/lib/resolvers/post_mutations/likeComment.js b/lib/resolvers/post_mutations/likeComment.js index 74346036b7..1a40b75d18 100644 --- a/lib/resolvers/post_mutations/likeComment.js +++ b/lib/resolvers/post_mutations/likeComment.js @@ -1,10 +1,11 @@ const User = require('../../models/User'); -const Comment = require('../../models/Comment'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const likeComment = async (parent, args, context) => { + const { db, id, tenantId } = await tenantCtx(args.id); const user = await User.findById(context.userId); if (!user) { throw new NotFoundError( @@ -13,8 +14,9 @@ const likeComment = async (parent, args, context) => { 'user' ); } + const { Comment } = db; - let comment = await Comment.findById(args.id); + let comment = await Comment.findById(id); if (!comment) { throw new NotFoundError( requestContext.translate('comment.notFound'), @@ -24,12 +26,14 @@ const likeComment = async (parent, args, context) => { } if (!comment.likedBy.includes(context.userId)) { let newComment = await Comment.findByIdAndUpdate( - args.id, + id, { $push: { likedBy: user }, $inc: { likeCount: 1 } }, { new: true } ); + newComment._doc._id = addTenantId(newComment._id, tenantId); return newComment; } + comment._doc._id = addTenantId(comment._id, tenantId); return comment; }; diff --git a/lib/resolvers/post_mutations/likePost.js b/lib/resolvers/post_mutations/likePost.js index 6830a6a657..cec0c71cff 100644 --- a/lib/resolvers/post_mutations/likePost.js +++ b/lib/resolvers/post_mutations/likePost.js @@ -1,10 +1,11 @@ const User = require('../../models/User'); -const Post = require('../../models/Post'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const likePost = async (parent, args, context) => { + const { db, id, tenantId } = await tenantCtx(args.id); const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -14,7 +15,8 @@ const likePost = async (parent, args, context) => { ); } - const post = await Post.findOne({ _id: args.id }); + const { Post } = db; + const post = await Post.findOne({ _id: id }); if (!post) { throw new NotFoundError( requestContext.translate('post.notFound'), @@ -25,7 +27,7 @@ const likePost = async (parent, args, context) => { if (!post.likedBy.includes(context.userId)) { const newPost = await Post.findOneAndUpdate( - { _id: args.id }, + { _id: id }, { $push: { likedBy: user, @@ -36,8 +38,10 @@ const likePost = async (parent, args, context) => { }, { new: true } ); + newPost._doc._id = addTenantId(newPost._id, tenantId); return newPost; } + post._doc._id = addTenantId(post._id, tenantId); return post; }; diff --git a/lib/resolvers/post_mutations/removeComment.js b/lib/resolvers/post_mutations/removeComment.js index a75b40f082..bf9c1b5311 100644 --- a/lib/resolvers/post_mutations/removeComment.js +++ b/lib/resolvers/post_mutations/removeComment.js @@ -1,11 +1,12 @@ const User = require('../../models/User'); -const Comment = require('../../models/Comment'); -const Post = require('../../models/Post'); +const tenantCtx = require('../../helper_functions/tenantCtx'); +const { addTenantId } = require('../../helper_functions/'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const removeComment = async (parent, args, context) => { + const { db, id, tenantId } = await tenantCtx(args.id); const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -14,8 +15,8 @@ const removeComment = async (parent, args, context) => { 'user' ); } - - const comment = await Comment.findOne({ _id: args.id }); + const { Comment, Post } = db; + const comment = await Comment.findOne({ _id: id }); if (!comment) { throw new NotFoundError( requestContext.translate('comment.notFound'), @@ -36,7 +37,7 @@ const removeComment = async (parent, args, context) => { { _id: comment.post }, { $pull: { - comments: args.id, + comments: id, }, $inc: { commentCount: -1, @@ -44,7 +45,8 @@ const removeComment = async (parent, args, context) => { } ); - await Comment.deleteOne({ _id: args.id }); + await Comment.deleteOne({ _id: id }); + comment._doc._id = addTenantId(comment._id, tenantId); return comment; }; diff --git a/lib/resolvers/post_mutations/removePost.js b/lib/resolvers/post_mutations/removePost.js index c1e3ee63e6..a0b458f2a0 100644 --- a/lib/resolvers/post_mutations/removePost.js +++ b/lib/resolvers/post_mutations/removePost.js @@ -1,10 +1,12 @@ const User = require('../../models/User'); -const Post = require('../../models/Post'); +const tenantCtx = require('../../helper_functions/tenantCtx'); +const { addTenantId } = require('../../helper_functions/'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const removePost = async (parent, args, context) => { + const { id, db, tenantId } = await tenantCtx(args.id); const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -14,7 +16,9 @@ const removePost = async (parent, args, context) => { ); } - const post = await Post.findOne({ _id: args.id }); + const { Post } = db; + + const post = await Post.findOne({ _id: id }); if (!post) { throw new NotFoundError( requestContext.translate('post.notFound'), @@ -31,7 +35,8 @@ const removePost = async (parent, args, context) => { ); } - await Post.deleteOne({ _id: args.id }); + await Post.deleteOne({ _id: id }); + post._doc._id = addTenantId(post._id, tenantId); return post; }; diff --git a/lib/resolvers/post_mutations/unlikeComment.js b/lib/resolvers/post_mutations/unlikeComment.js index 2492fd1023..6ef9085a28 100644 --- a/lib/resolvers/post_mutations/unlikeComment.js +++ b/lib/resolvers/post_mutations/unlikeComment.js @@ -1,10 +1,11 @@ const User = require('../../models/User'); -const Comment = require('../../models/Comment'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const unlikeComment = async (parent, args, context) => { + const { db, id, tenantId } = await tenantCtx(args.id); const user = await User.findById(context.userId); if (!user) { throw new NotFoundError( @@ -13,8 +14,8 @@ const unlikeComment = async (parent, args, context) => { 'user' ); } - - let comment = await Comment.findById(args.id); + const { Comment } = db; + let comment = await Comment.findById(id); if (!comment) { throw new NotFoundError( requestContext.translate('comment.notFound'), @@ -24,12 +25,14 @@ const unlikeComment = async (parent, args, context) => { } if (comment.likedBy.includes(context.userId)) { let newComment = await Comment.findByIdAndUpdate( - args.id, + id, { $pull: { likedBy: context.userId }, $inc: { likeCount: -1 } }, { new: true } ); + newComment._doc._id = addTenantId(newComment._id, tenantId); return newComment; } + comment._doc._id = addTenantId(comment._id, tenantId); return comment; }; diff --git a/lib/resolvers/post_mutations/unlikePost.js b/lib/resolvers/post_mutations/unlikePost.js index 54d1299790..4ba4bda9f0 100644 --- a/lib/resolvers/post_mutations/unlikePost.js +++ b/lib/resolvers/post_mutations/unlikePost.js @@ -1,10 +1,11 @@ const User = require('../../models/User'); -const Post = require('../../models/Post'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const unlikePost = async (parent, args, context) => { + const { db, id, tenantId } = await tenantCtx(args.id); const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -13,8 +14,9 @@ const unlikePost = async (parent, args, context) => { 'user' ); } + const { Post } = db; - const post = await Post.findOne({ _id: args.id }); + const post = await Post.findOne({ _id: id }); if (!post) { throw new NotFoundError( requestContext.translate('post.notFound'), @@ -24,20 +26,21 @@ const unlikePost = async (parent, args, context) => { } if (post.likedBy.includes(context.userId)) { const newPost = await Post.findOneAndUpdate( - { _id: args.id }, + { _id: id }, { $pull: { likedBy: context.userId, }, $inc: { - commentCount: -1, + likeCount: -1, }, }, { new: true } ); - + newPost._doc._id = addTenantId(newPost._id, tenantId); return newPost; } + post._doc._id = addTenantId(post._id, tenantId); return post; }; diff --git a/lib/resolvers/post_query/comments.js b/lib/resolvers/post_query/comments.js index ab56a439b6..af623ebcda 100644 --- a/lib/resolvers/post_query/comments.js +++ b/lib/resolvers/post_query/comments.js @@ -1,13 +1,24 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); -const Comment = require('../../models/Comment'); +const User = require('../../models/User'); module.exports = async () => { - const commentFound = await Comment.find() - .populate('creator', '-password') - .populate('post') - .populate('likedBy'); + // loop through all open connections + let commentFound = []; + const connections = getAllConnections(); + for (let conn in connections) { + const comments = await connections[conn].Comment.find() + .populate('creator', '-password', User) + .populate('post', connections[conn].Post) + .populate('likedBy', '', User); + for (let i = 0; i < comments.length; i++) { + comments[i]._doc._id = addTenantId(comments[i]._id, conn); + commentFound.push(comments[i]); + } + } if (!commentFound) { throw new NotFoundError( requestContext.translate('comment.notFound'), diff --git a/lib/resolvers/post_query/commentsByPost.js b/lib/resolvers/post_query/commentsByPost.js index 4a6a51107a..8136093ecd 100644 --- a/lib/resolvers/post_query/commentsByPost.js +++ b/lib/resolvers/post_query/commentsByPost.js @@ -1,8 +1,9 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); -const Comment = require('../../models/Comment'); const Organization = require('../../models/Organization'); +const User = require('../../models/User'); const { COMMENT_NOT_FOUND, @@ -29,10 +30,12 @@ const { } = require('../../../constants'); module.exports = async (parent, args) => { - const commentFound = await Comment.find({ post: args.id }) - .populate('creator', '-password') - .populate('post') - .populate('likedBy'); + const { db, id, tenantId } = await tenantCtx(args.id); + const { Comment, Post } = db; + const commentFound = await Comment.find({ post: id }) + .populate('creator', '-password', User) + .populate('likedBy', '', User) + .populate('post', '', Post); //comment does not exist if (!commentFound.length) { throw new NotFoundError( @@ -76,5 +79,9 @@ module.exports = async (parent, args) => { ORGANIZATION_NOT_FOUND_PARAM ); } + + for (let i = 0; i < commentFound.length; i++) { + commentFound[i]._doc._id = addTenantId(commentFound[i]._id, tenantId); + } return commentFound; }; diff --git a/lib/resolvers/post_query/post.js b/lib/resolvers/post_query/post.js index b86692e193..e28fe640d2 100644 --- a/lib/resolvers/post_query/post.js +++ b/lib/resolvers/post_query/post.js @@ -1,21 +1,27 @@ const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); +const { addTenantId, tenantCtx } = require('../../helper_functions'); -const Post = require('../../models/Post'); +const User = require('../../models/User'); +const Organization = require('../../models/Organization'); module.exports = async (parent, args) => { + const { id, db, tenantId } = await tenantCtx(args.id); + const { Post } = db; const postFound = await Post.findOne({ - _id: args.id, + _id: id, }) - .populate('organization') + .populate('creator', '-password', User) .populate({ path: 'comments', populate: { path: 'creator', + model: User, }, }) - .populate('likedBy') - .populate('creator', '-password'); + .populate('likedBy', '', User) + .populate('organization', '', Organization); + if (!postFound) { throw new NotFoundError( process.env.NODE_ENV !== 'production' @@ -27,5 +33,6 @@ module.exports = async (parent, args) => { } postFound.likeCount = postFound.likedBy.length || 0; postFound.commentCount = postFound.comments.length || 0; + postFound._doc._id = addTenantId(postFound._id, tenantId); return postFound; }; diff --git a/lib/resolvers/post_query/posts.js b/lib/resolvers/post_query/posts.js index ab76a8a3fa..c3f9285b09 100644 --- a/lib/resolvers/post_query/posts.js +++ b/lib/resolvers/post_query/posts.js @@ -1,5 +1,7 @@ -const Post = require('../../models/Post'); - +const Organization = require('../../models/Post'); +const User = require('../../models/Post'); +const { getAllConnections } = require('../../ConnectionManager/connections'); +const { addTenantId } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; var isSortingExecuted = args.orderBy !== null; @@ -40,17 +42,26 @@ module.exports = async (parent, args) => { sort = { commentCount: -1 }; } } - const p = await Post.find() - .sort(sort) - .populate('organization') - .populate('likedBy') - .populate({ - path: 'comments', - populate: { - path: 'creator', - }, - }) - .populate('creator', '-password'); + let p = []; + const connections = getAllConnections(); + for (let conn in connections) { + const posts = await connections[conn].Post.find() + .populate('organization', '', Organization) + .populate('likedBy', '', User) + .populate({ + path: 'comments', + populate: { + path: 'creator', + model: User, + }, + }) + .populate('creator', '-password', User) + .sort(sort); + for (let i = 0; i < posts.length; i++) { + posts[i]._doc._id = addTenantId(posts[i]._id, conn); + p.push(posts[i]); + } + } const posts = p.map((post) => { post.likeCount = post.likedBy.length || 0; post.commentCount = post.comments.length || 0; diff --git a/lib/resolvers/post_query/postsByOrganization.js b/lib/resolvers/post_query/postsByOrganization.js index 6351e9eb32..7b42b2628b 100644 --- a/lib/resolvers/post_query/postsByOrganization.js +++ b/lib/resolvers/post_query/postsByOrganization.js @@ -1,6 +1,11 @@ -const Post = require('../../models/Post'); +const Organization = require('../../models/Post'); +const User = require('../../models/User'); +const { addTenantId } = require('../../helper_functions'); +const getTenantConnection = require('../../ConnectionManager/getTenantConnection'); +const { orgHasTenant } = require('../../ConnectionManager/connections'); module.exports = async (parent, args) => { + const db = await getTenantConnection(args.id); var sort = {}; var isSortingExecuted = args.orderBy !== null; @@ -40,18 +45,25 @@ module.exports = async (parent, args) => { sort = { commentCount: -1 }; } } - - return Post.find({ + const { Post } = db; + const posts = await Post.find({ organization: args.id, }) - .sort(sort) - .populate('organization') - .populate('likedBy') + .populate('organization', '', Organization) + .populate('likedBy', '', User) .populate({ path: 'comments', populate: { path: 'creator', + model: User, }, }) - .populate('creator', '-password'); + .populate('creator', '-password', User) + .sort(sort); + if (orgHasTenant(args.id)) { + for (let i = 0; i < posts.length; i++) { + posts[i]._doc._id = addTenantId(posts[i]._doc._id, args.id); + } + } + return posts; }; diff --git a/lib/resolvers/project_task_mutations/createTask.js b/lib/resolvers/project_task_mutations/createTask.js index c961758d12..9d46111f88 100644 --- a/lib/resolvers/project_task_mutations/createTask.js +++ b/lib/resolvers/project_task_mutations/createTask.js @@ -1,6 +1,5 @@ const User = require('../../models/User'); -const Task = require('../../models/Task'); -const Event = require('../../models/Event'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); const { NotFoundError } = require('errors'); const requestContext = require('talawa-request-context'); const { @@ -15,6 +14,8 @@ const { EVENT_NOT_FOUND_PARAM, } = require('../../../constants'); const createTask = async (parent, args, context) => { + const { id: eventId, db, tenantId } = await tenantCtx(args.eventId); + const { Event, Task } = db; // gets user in token - to be used later on const user = await User.findOne({ _id: context.userId }); if (!user) { @@ -28,7 +29,7 @@ const createTask = async (parent, args, context) => { } const eventFound = await Event.findOne({ - _id: args.eventId, + _id: eventId, }); if (!eventFound) { throw new NotFoundError( @@ -42,20 +43,29 @@ const createTask = async (parent, args, context) => { const task = new Task({ ...args.data, - event: eventFound, - creator: user, + event: eventFound._id, + creator: user._id, }); await task.save(); await Event.findOneAndUpdate( - { _id: args.eventId }, + { _id: eventId }, { $push: { - tasks: task, + tasks: task._id, }, }, { new: true } ); + task._doc._id = addTenantId(task._id, tenantId); + await User.updateOne( + { _id: user.id }, + { + $push: { + createdTasks: task._doc._id, + }, + } + ); return { ...task._doc, }; diff --git a/lib/resolvers/project_task_mutations/removeTask.js b/lib/resolvers/project_task_mutations/removeTask.js index 4fe1e880ae..fd7d6426ef 100644 --- a/lib/resolvers/project_task_mutations/removeTask.js +++ b/lib/resolvers/project_task_mutations/removeTask.js @@ -1,10 +1,11 @@ const User = require('../../models/User'); -const Event = require('../../models/Event'); -const Task = require('../../models/Task'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const removeTask = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { Event, Task } = db; const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -14,7 +15,7 @@ const removeTask = async (parent, args, context) => { ); } - const foundTask = await Task.findOne({ _id: args.id }); + const foundTask = await Task.findOne({ _id: id }); if (!foundTask) { throw new NotFoundError( requestContext.translate('task.notFound'), @@ -35,12 +36,13 @@ const removeTask = async (parent, args, context) => { { id: foundTask.event }, { $pull: { - tasks: args.id, + tasks: id, }, } ); - await Task.deleteOne({ _id: args.id }); + await Task.deleteOne({ _id: id }); + foundTask._doc._id = args.id; return foundTask; }; diff --git a/lib/resolvers/project_task_mutations/updateTask.js b/lib/resolvers/project_task_mutations/updateTask.js index 2de780bf8d..c337855398 100644 --- a/lib/resolvers/project_task_mutations/updateTask.js +++ b/lib/resolvers/project_task_mutations/updateTask.js @@ -1,10 +1,12 @@ const User = require('../../models/User'); -const Task = require('../../models/Task'); +const { tenantCtx } = require('../../helper_functions'); const { NotFoundError, UnauthorizedError } = require('errors'); const requestContext = require('talawa-request-context'); const updateTask = async (parent, args, context) => { + const { id, db } = await tenantCtx(args.id); + const { Task } = db; const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( @@ -14,7 +16,7 @@ const updateTask = async (parent, args, context) => { ); } - const task = await Task.findOne({ _id: args.id }); + const task = await Task.findOne({ _id: id }); if (!task) { throw new NotFoundError( requestContext.translate('task.notFound'), @@ -32,10 +34,11 @@ const updateTask = async (parent, args, context) => { } const newTask = await Task.findOneAndUpdate( - { _id: args.id }, + { _id: id }, { ...args.data }, { new: true } ); + newTask._doc._id = args.id; return newTask; }; diff --git a/lib/resolvers/user_query/tasksByUser.js b/lib/resolvers/user_query/tasksByUser.js index 93dccddb12..0fae76cfbd 100644 --- a/lib/resolvers/user_query/tasksByUser.js +++ b/lib/resolvers/user_query/tasksByUser.js @@ -1,4 +1,5 @@ -const Task = require('../../models/Task'); +const User = require('../../models/User'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); module.exports = async (parent, args) => { var sort = {}; @@ -29,8 +30,24 @@ module.exports = async (parent, args) => { } } - return await Task.find({ creator: args.id }) - .sort(sort) - .populate('event') - .populate('creator', '-password'); + const user = await User.findById(args.id); + let tasks = []; + const orgs = {}; + for (let i = 0; i < user.createdTasks.length; i++) { + const { tenantId, db } = await tenantCtx(user.createdTasks[i]); + if (tenantId in orgs) continue; + orgs[tenantId] = true; + const { Task } = db; + const curTasks = await Task.find({ creator: args.id }) + .sort(sort) + .populate('event') + .populate('creator', '-password', User); + + for (let i = 0; i < curTasks.length; i++) { + curTasks[i]._doc._id = addTenantId(curTasks[i]._doc._id, tenantId); + tasks.push(curTasks[i]); + } + } + + return tasks; }; diff --git a/lib/resolvers/user_query/users.js b/lib/resolvers/user_query/users.js index d803c26539..3800c9d729 100644 --- a/lib/resolvers/user_query/users.js +++ b/lib/resolvers/user_query/users.js @@ -4,6 +4,31 @@ const filterParamUtil = require('./user_filter'); const orderByFilter = require('./user_orderBy'); const User = require('../../models/User'); const userExists = require('../../helper_functions/userExists'); +const { tenantCtx, addTenantId } = require('../../helper_functions'); + +// this function is used to populate the users event data specifically from other tenants +// replacing the old populate because it won't work with multi tenancy architecture. +const customArrayPopulate = async (object, field, model) => { + let items = []; + const orgs = {}; + for (let i = 0; i < object[field].length; i++) { + const { tenantId, db } = await tenantCtx(object[field][i]); + if (tenantId in orgs) continue; + orgs[tenantId] = true; + const curModels = await db[model].find({ + registrants: { + $elemMatch: { + userId: object._id, + }, + }, + }); + for (let i = 0; i < curModels.length; i++) { + curModels[i]._doc._id = addTenantId(curModels[i]._doc._id, tenantId); + items.push(curModels[i]); + } + } + object._doc[field] = items; +}; // Query to provide logged user information const me = async (parent, args, context) => { @@ -11,11 +36,9 @@ const me = async (parent, args, context) => { _id: context.userId, }) .populate('createdOrganizations') - .populate('createdEvents') .populate('joinedOrganizations') - .populate('registeredEvents') - .populate('eventAdmin') .populate('adminFor'); + if (!user) { throw new NotFoundError( requestContext.translate('user.notFound'), @@ -24,6 +47,10 @@ const me = async (parent, args, context) => { ); } + await customArrayPopulate(user, 'eventAdmin', 'Event'); + await customArrayPopulate(user, 'createdEvents', 'Event'); + await customArrayPopulate(user, 'registeredEvents', 'Event'); + return { ...user._doc, password: null, @@ -63,11 +90,16 @@ const users = async (parent, args) => { const users = await User.find(inputArg) .sort(sort) .populate('createdOrganizations') - .populate('createdEvents') .populate('joinedOrganizations') - .populate('registeredEvents') - .populate('eventAdmin') .populate('adminFor'); + + for (let i = 0; i < users.length; i++) { + let user = users[i]; + await customArrayPopulate(user, 'eventAdmin', 'Event'); + await customArrayPopulate(user, 'createdEvents', 'Event'); + await customArrayPopulate(user, 'registeredEvents', 'Event'); + } + if (!users[0]) { throw new NotFoundError( requestContext.translate('user.notFound'), @@ -103,12 +135,16 @@ const usersConnection = async (parent, args) => { .limit(args.first) .skip(args.skip) .populate('createdOrganizations') - .populate('createdEvents') .populate('joinedOrganizations') - .populate('registeredEvents') - .populate('eventAdmin') .populate('adminFor'); + for (let i = 0; i < users.length; i++) { + let user = users[i]; + await customArrayPopulate(user, 'eventAdmin', 'Event'); + await customArrayPopulate(user, 'createdEvents', 'Event'); + await customArrayPopulate(user, 'registeredEvents', 'Event'); + } + return users.map((user) => { return { ...user._doc, @@ -203,4 +239,5 @@ module.exports = { users: users, usersConnection: usersConnection, organizationsMemberConnection: organizationsMemberConnection, + customArrayPopulate, }; diff --git a/lib/schema/organization/organization.graphql b/lib/schema/organization/organization.graphql index 59615df6e2..6493c83750 100644 --- a/lib/schema/organization/organization.graphql +++ b/lib/schema/organization/organization.graphql @@ -20,6 +20,7 @@ module.exports = ` apiUrl:String! createdAt:String tags: [String] + functionality: Functionality! } type OrganizationInfoNode { @@ -33,6 +34,21 @@ module.exports = ` apiUrl:String! } + input FunctionalityInput { + DirectChat: Boolean! + Posts: Boolean! + Events: Boolean! + Groups: Boolean! + GroupChats: Boolean! + } + type Functionality { + DirectChat: Boolean! + Posts: Boolean! + Events: Boolean! + Groups: Boolean! + GroupChats: Boolean! + } + input OrganizationInput { name:String! description: String! @@ -41,7 +57,10 @@ module.exports = ` isPublic: Boolean! visibleInSearch: Boolean! apiUrl:String + schema: [String] tags: [String] + customScheme: Boolean + functionality: FunctionalityInput } diff --git a/package-lock.json b/package-lock.json index 2f10d31994..89b052d844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13761,8 +13761,7 @@ "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "requires": {} + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } }, @@ -14407,8 +14406,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -14733,8 +14731,7 @@ "apollo-server-errors": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", - "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", - "requires": {} + "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==" }, "apollo-server-express": { "version": "2.25.3", @@ -14804,8 +14801,7 @@ "apollo-server-errors": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz", - "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==", - "requires": {} + "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==" }, "apollo-server-plugin-base": { "version": "0.13.0", @@ -16315,8 +16311,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -17245,8 +17240,7 @@ "graphql-ws": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.6.4.tgz", - "integrity": "sha512-5r8tAzznI1zeh7k12+3z07KkgXPckQbnC9h4kJ2TBDWG2wb26TJTbVHQOiAncDBgPbtXtc1A2BlttiRuPH2t/w==", - "requires": {} + "integrity": "sha512-5r8tAzznI1zeh7k12+3z07KkgXPckQbnC9h4kJ2TBDWG2wb26TJTbVHQOiAncDBgPbtXtc1A2BlttiRuPH2t/w==" }, "gtoken": { "version": "5.3.2", @@ -18153,8 +18147,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "27.4.0", @@ -19180,8 +19173,7 @@ "mongoose-legacy-pluralize": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "requires": {} + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" }, "mongoose-paginate-v2": { "version": "1.5.0", @@ -21652,8 +21644,7 @@ "ws": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "requires": {} + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==" }, "xdg-basedir": { "version": "4.0.0", diff --git a/tests/database/mongoImplementation.spec.js b/tests/database/mongoImplementation.spec.js new file mode 100644 index 0000000000..982b3ef1f5 --- /dev/null +++ b/tests/database/mongoImplementation.spec.js @@ -0,0 +1,73 @@ +const Database = require('../../lib/Database/index'); +//const User = require('../../lib/Database/MongoImplementation/schema/User'); +const { DATABASE_CONNECTION_FAIL } = require('../../constants'); + +const firstUrl = 'mongodb://localhost:27017/db1?retryWrites=true&w=majority'; +const secondUrl = 'mongodb://localhost:27017/db2?retryWrites=true&w=majority'; + +// not using an options.schema would result in creating a database +// with all valid schema parts. +const firstDB = new Database(firstUrl); +const secondDB = new Database(secondUrl, { + schema: ['User'], +}); + +const FirstUserObject = firstDB.User; +const SecondUserObject = secondDB.User; + +beforeAll(async () => { + await firstDB.connect(); + await secondDB.connect(); +}); + +afterAll(async () => { + await firstDB.User.deleteMany({}); + await secondDB.User.deleteMany({}); + await firstDB.disconnect(); + await secondDB.disconnect(); +}); + +describe('testing multiple databases functionality', () => { + test('inavlid url throws an error', async () => { + await expect(async () => { + const db = new Database('some_invalid_url'); + await db.connect(); + }).rejects.toThrow(DATABASE_CONNECTION_FAIL); + }); + + test('first db is working', async () => { + const firstDbUser = new FirstUserObject({ + firstName: 'test', + lastName: 'test', + email: 'test@gmail.com', + password: 'testpassword', + }); + + await firstDbUser.save(); + let usr1 = await FirstUserObject.findOne({ firstName: 'test' }); + let usr2 = await SecondUserObject.findOne({ firstName: 'test' }); + expect(usr1.firstName).toBe('test'); + expect(usr2).toBeFalsy(); + await FirstUserObject.deleteMany({ firstName: 'test' }); + usr1 = await FirstUserObject.findOne({ firstName: 'test' }); + expect(usr1).toBeFalsy(); + }); + + test('second db is working', async () => { + const secondDbUser = new SecondUserObject({ + firstName: 'test', + lastName: 'test', + email: 'test@gmail.com', + password: 'testpassword', + }); + + await secondDbUser.save(); + let usr1 = await FirstUserObject.findOne({ firstName: 'test' }); + let usr2 = await SecondUserObject.findOne({ firstName: 'test' }); + expect(usr2.firstName).toBe('test'); + expect(usr1).toBeFalsy(); + await SecondUserObject.deleteMany({ firstName: 'test' }); + usr2 = await SecondUserObject.findOne({ firstName: 'test' }); + expect(usr2).toBeFalsy(); + }); +}); diff --git a/tests/database/multi-tenancy.spec.js b/tests/database/multi-tenancy.spec.js new file mode 100644 index 0000000000..6c8cb1e0d8 --- /dev/null +++ b/tests/database/multi-tenancy.spec.js @@ -0,0 +1,162 @@ +const shortid = require('shortid'); +const Tenant = require('../../lib/models/Tenant'); +const connectionManager = require('../../lib/ConnectionManager'); +const { + Types: { ObjectId }, +} = require('mongoose'); +const { ORGANIZATION_NOT_FOUND } = require('../../constants'); + +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({ + url: tenantUrl, + organization: organizationId, + }); + await tenant.save(); +}); + +afterAll(async () => { + const conn1 = await connectionManager.getTenantConnection(organizationId); + const conn2 = await 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('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('adding invalid org connection', async () => { + // throws the correct error on invalid org id. + await expect(async () => { + await connectionManager.addTenantConnection(new ObjectId()); + }).rejects.toThrow(ORGANIZATION_NOT_FOUND); + }); + + test('getConnection', async () => { + const conn = await 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 = await connectionManager.getTenantConnection(organizationId); + const conn2 = await connectionManager.getTenantConnection( + secondOrganizationId + ); + + const firstOrgPosts = await conn1.Post.find(); + const secondOrgPosts = await conn2.Post.find(); + + expect(firstOrgPosts).toHaveLength(1); + expect(secondOrgPosts).toHaveLength(0); + }); +}); diff --git a/tests/resolvers/direct_chat_query/directChatsByUserID.spec.js b/tests/resolvers/direct_chat_query/directChatsByUserID.spec.js index 84ac1648ef..f56eaec011 100644 --- a/tests/resolvers/direct_chat_query/directChatsByUserID.spec.js +++ b/tests/resolvers/direct_chat_query/directChatsByUserID.spec.js @@ -77,6 +77,18 @@ const createDirectChat = async (user, users, organization) => { }); const createdDirectChat = await newDirectChat.save(); + for (let user of users) { + await User.updateOne( + { + _id: user._doc._id, + }, + { + $push: { + directChats: createdDirectChat._doc._id, + }, + } + ); + } return createdDirectChat; }; diff --git a/tests/resolvers/event_project_mutations/updateProject.spec.js b/tests/resolvers/event_project_mutations/updateProject.spec.js index 64b5060503..099e7964bf 100644 --- a/tests/resolvers/event_project_mutations/updateProject.spec.js +++ b/tests/resolvers/event_project_mutations/updateProject.spec.js @@ -99,9 +99,9 @@ const createEvent = async (userId, organizationId) => { { _id: userId }, { $push: { - eventAdmin: newEvent, - createdEvents: newEvent, - registeredEvents: newEvent, + eventAdmin: newEvent._id, + createdEvents: newEvent._id, + registeredEvents: newEvent._id, }, } ); diff --git a/tests/resolvers/event_query/registeredEventsByUser.spec.js b/tests/resolvers/event_query/registeredEventsByUser.spec.js index bf2b813958..548e146835 100644 --- a/tests/resolvers/event_query/registeredEventsByUser.spec.js +++ b/tests/resolvers/event_query/registeredEventsByUser.spec.js @@ -1,9 +1,16 @@ const registeredEventsByUser = require('../../../lib/resolvers/event_query/registeredEventsByUser'); const database = require('../../../db'); +const getUserId = require('../../functions/getUserId'); +const getToken = require('../../functions/getToken'); +const shortid = require('shortid'); +let userId; beforeAll(async () => { require('dotenv').config(); // pull env variables from .env file await database.connect(); + let generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + await getToken(generatedEmail); + userId = await getUserId(generatedEmail); }); afterAll(() => { @@ -38,6 +45,7 @@ describe('Unit testing', () => { test(`Registered Events by User Query with orderBy ${arg}`, async () => { const args = { orderBy: arg, + id: userId, }; const response = await registeredEventsByUser({}, args); response.map((event) => {