diff --git a/.env.sample b/.env.sample index 00ed0a8bc6..1f901e4b7b 100644 --- a/.env.sample +++ b/.env.sample @@ -17,6 +17,29 @@ REFRESH_TOKEN_SECRET= MONGO_DB_URL= +# Get the google recaptcha secret key from google recaptcha admin or https://www.google.com/recaptcha/admin/create +# from here for reCAPTCHA v2 and "I'm not a robot" Checkbox, and paste the key here. +# Note: In domains, fill localhost + +RECAPTCHA_SECRET_KEY= + +# Enter gmail credentials here for sending mails to the users. In MAIL_USERNAME enter email address and in MAIL_PASSWORD +# enter app password. +# To get the app password, follow these steps: +# 1. Go to your Google Account, https://myaccount.google.com/ +# 2. Select Security. +# 3. Under "Signing in to Google," select App Passwords. +# 4. At the bottom, choose Select app and choose the app you using and then Select device and choose the device you’re using and then Generate. +# 5. The App Password is the 16-character code in the yellow bar on your device. +# 6. Paste that App Password in MAIL_PASSWORD. + +# Note: You must setup two factor authentication in order to allow the app password. + +# For more info refer, https://support.google.com/accounts/answer/185833 + +MAIL_USERNAME= +MAIL_PASSWORD= + # Replace these values with instructions from INSTALLATION.md # androidFirebaseOptions apiKey=AIzaSyBkqgTkg2yNRsl7jIx_EtCyF9YqiCJX7sz @@ -32,4 +55,4 @@ iOSmessagingSenderId=803598513727 iOSprojectId=talawa-352607 iOSstorageBucket=talawa-352607.appspot.com iosClientId=803598513727-2gt176dpe0ljn5ie967o4d0rm0vo8sm0.apps.googleusercontent.com -iosBundleId=com.talawa.app \ No newline at end of file +iosBundleId=com.talawa.app diff --git a/.prettierrc b/.prettierrc index dc2fb828f0..2c0fc022ce 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "singleQuote": true + "singleQuote": true, + "endOfLine": "auto" } \ No newline at end of file diff --git a/INSTALLATION.md b/INSTALLATION.md index 2b59cd4057..a5829fd2cf 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -110,19 +110,42 @@ Follow these steps to get the API running. - `` is your cloud service database name. * Your cloud service may call this URL a `connect` string. Search for the word `connect` in your online database dashboard to find it. -8. When finished, your `.env` file should have the following fields filled in. +8. Get the google recaptcha secret key from `google-recaptcha-admin` for reCAPTCHA v2 and "I'm not a robot" Checkbox, and copy the key to the `RECAPTCHA_SECRET_KEY` section of the `.env` file. + +- Note: In domains, fill localhost + + Google-recaptcha-admin: https://www.google.com/recaptcha/admin/create + +9. Enter the gmail credentials for sending mails to the users. In `MAIL_USERNAME` enter email address and in `MAIL_PASSWORD` enter app password. +- To get the app password, follow these steps: + + * Go to your Google Account, https://myaccount.google.com/ + * Select Security. + * Under "Signing in to Google," select App Passwords. + * At the bottom, choose Select app and choose the app you using and then Select device and choose the device you’re using and then Generate. + * The App Password is the 16-character code in the yellow bar on your device. + * Paste that App Password in `MAIL_PASSWORD`. + +- Note: You must setup two factor authentication in order to allow the app password. + +- For more info refer, https://support.google.com/accounts/answer/185833 + +10. When finished, your `.env` file should have the following fields filled in. - ACCESS_TOKEN_SECRET - REFRESH_TOKEN_SECRET - MONGO_DB_URL + - RECAPTCHA_SECRET_KEY + - MAIL_USERNAME + - MAIL_PASSWORD Please review the contents of the `.env.sample` file for additional details. -9. Install required node packages. +11. Install required node packages. npm install -10. To test the notification service create a new firebase project: +12. To test the notification service create a new firebase project: - In the Firebase console, open Settings > [Service Accounts](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk). @@ -149,11 +172,11 @@ Follow these steps to get the API running. 7. Copy the keys to `.env` file, for how to set keys refer to `.env.sample` file. 8. Undo the changes made to the `firebase_options.dart` file by pasting the old content from step 2. -11. Start the `talawa-api` server using the below command in the same terminal. +13. Start the `talawa-api` server using the below command in the same terminal. npm start -11. To stop the server after making changes. Press `CTRL + C` in the terminal where the above command is executed. +14. To stop the server after making changes. Press `CTRL + C` in the terminal where the above command is executed. # Testing You can run `talawa-api` tests using this command diff --git a/constants.js b/constants.js index e45be9df3d..2ba9c2bd8d 100644 --- a/constants.js +++ b/constants.js @@ -25,6 +25,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'; @@ -45,6 +47,10 @@ const EVENT_NOT_FOUND_MESSAGE = 'event.notFound'; const EVENT_NOT_FOUND_CODE = 'event.notFound'; const EVENT_NOT_FOUND_PARAM = 'event'; +const ERROR_IN_SENDING_MAIL = 'Error in sending mail'; + +const INVALID_OTP = 'Invalid OTP'; + const REGISTRANT_ALREADY_EXIST = 'Already registered for the event'; const REGISTRANT_ALREADY_EXIST_MESSAGE = 'registrant.alreadyExist'; const REGISTRANT_ALREADY_EXIST_CODE = 'registrant.alreadyExist'; @@ -75,6 +81,8 @@ 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'; @@ -104,6 +112,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, @@ -114,6 +124,10 @@ module.exports = { EVENT_NOT_FOUND_CODE, EVENT_NOT_FOUND_PARAM, + ERROR_IN_SENDING_MAIL, + + INVALID_OTP, + USER_ALREADY_MEMBER, USER_ALREADY_MEMBER_CODE, USER_ALREADY_MEMBER_MESSAGE, @@ -163,4 +177,6 @@ module.exports = { POST_NOT_FOUND_MESSAGE, POST_NOT_FOUND_CODE, POST_NOT_FOUND_PARAM, + + DATABASE_CONNECTION_FAIL, }; diff --git a/image/README.md b/image/README.md new file mode 100644 index 0000000000..e69de29bb2 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 index f9c8f23345..a777c360e4 100644 --- a/lib/ConnectionManager/addTenantConnection.js +++ b/lib/ConnectionManager/addTenantConnection.js @@ -1,19 +1,29 @@ const Tenant = require('../models/Tenant'); const Database = require('../Database/index'); -const { setConnection, getConnection } = require('./connections'); +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) => { - try { - const alreadyConnected = getConnection(organizationId); - if (alreadyConnected) return alreadyConnected; - const [tenant] = await Tenant.find({ organization: organizationId }); - if (!tenant || !tenant.url) throw new Error('Organization not found!'); - console.log('tenant', tenant); - let connection = new Database(tenant.url); - await connection.connect(); - setConnection(tenant.organization, connection); - return connection; - } catch (e) { - console.log('organization not found!'); + 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); + await connection.connect(); + setConnection(tenant.organization, connection); + return connection; }; diff --git a/lib/ConnectionManager/connections.js b/lib/ConnectionManager/connections.js index 55a14f75ed..6506c78035 100644 --- a/lib/ConnectionManager/connections.js +++ b/lib/ConnectionManager/connections.js @@ -1,3 +1,6 @@ +const { CONNECTION_NOT_FOUND } = require('../../constants'); +const { NotFoundError } = require('errors'); + const connections = {}; const setConnection = (orgId, connection) => { @@ -5,7 +8,9 @@ const setConnection = (orgId, connection) => { }; const getConnection = (orgId) => { - if (!connections[orgId]) return null; + if (!connections[orgId]) { + throw new NotFoundError(CONNECTION_NOT_FOUND); + } return connections[orgId]; }; diff --git a/lib/ConnectionManager/destroyConnections.js b/lib/ConnectionManager/destroyConnections.js index d5e6a09968..635567fda2 100644 --- a/lib/ConnectionManager/destroyConnections.js +++ b/lib/ConnectionManager/destroyConnections.js @@ -1,9 +1,5 @@ const connections = require('./connections'); module.exports = async () => { - try { - await connections.destroy(); - } catch (e) { - console.log(e); - } + await connections.destroy(); }; diff --git a/lib/ConnectionManager/getTenantConnection.js b/lib/ConnectionManager/getTenantConnection.js index 3e0084356b..2e918082e6 100644 --- a/lib/ConnectionManager/getTenantConnection.js +++ b/lib/ConnectionManager/getTenantConnection.js @@ -1,9 +1,5 @@ const { getConnection } = require('./connections'); module.exports = (organizationId) => { - try { - return getConnection(organizationId); - } catch (e) { - console.log('organization not found!'); - } + return getConnection(organizationId); }; diff --git a/lib/Database/MongoImplementation/index.js b/lib/Database/MongoImplementation/index.js index 8dc07ab1ef..533950e128 100644 --- a/lib/Database/MongoImplementation/index.js +++ b/lib/Database/MongoImplementation/index.js @@ -1,5 +1,5 @@ const MongooseInstance = require('mongoose').Mongoose; -const logger = require('logger'); +const { DATABASE_CONNECTION_FAIL } = require('../../../constants'); const MessageChat = require('./schema/Chat'); const Comment = require('./schema/Comment'); const DirectChat = require('./schema/DirectChat'); @@ -60,8 +60,7 @@ function MongoDB(url, schemaObjects) { }); this.Native = this.mongo.connection.db; } catch (error) { - logger.error('Error while connecting to mongo database', error); - process.exit(1); + throw new Error(DATABASE_CONNECTION_FAIL); } }; this.disconnect = async () => { diff --git a/lib/helper_functions/addTenantId.js b/lib/helper_functions/addTenantId.js index 7189ca14f4..1abe7f146d 100644 --- a/lib/helper_functions/addTenantId.js +++ b/lib/helper_functions/addTenantId.js @@ -1,3 +1,3 @@ -module.exports = (tenantId, id) => { - return tenantId + ' ' + id; +module.exports = (tenantId, id, del = ' ') => { + return tenantId + del + id; }; diff --git a/lib/helper_functions/getTenantFromId.js b/lib/helper_functions/getTenantFromId.js index 75c1d59edc..c1e4d92747 100644 --- a/lib/helper_functions/getTenantFromId.js +++ b/lib/helper_functions/getTenantFromId.js @@ -1,4 +1,4 @@ -module.exports = (fullId) => { - const [tenantId, id] = fullId.split(' '); +module.exports = (fullId, del = ' ') => { + const [tenantId, id] = fullId.split(del); return { tenantId, id }; }; diff --git a/lib/helper_functions/mailer.js b/lib/helper_functions/mailer.js new file mode 100644 index 0000000000..4aa7d060b3 --- /dev/null +++ b/lib/helper_functions/mailer.js @@ -0,0 +1,33 @@ +const nodemailer = require('nodemailer'); + +const { ERROR_IN_SENDING_MAIL } = require('../../constants'); + +const mailer = (email, subject, body) => { + //NODEMAILER SPECIFIC STUFF + let transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.MAIL_USERNAME, + pass: process.env.MAIL_PASSWORD, + }, + }); + + let mailOptions = { + from: 'Talawa<>noreply@gmail.com', + to: email, + subject: subject, + html: body, + }; + + return new Promise((resolve, reject) => { + transporter.sendMail(mailOptions, function (err, info) { + if (err) { + reject(ERROR_IN_SENDING_MAIL); + } else { + resolve(info); + } + }); + }); +}; + +module.exports = mailer; diff --git a/lib/middleware/is-auth.js b/lib/middleware/is-auth.js index c80467588b..2523681cd0 100644 --- a/lib/middleware/is-auth.js +++ b/lib/middleware/is-auth.js @@ -77,5 +77,4 @@ const isAuth = (req) => { userId, }; }; - module.exports = isAuth; diff --git a/lib/models/Organization.js b/lib/models/Organization.js index 2b4931b474..0fad4dd5a5 100644 --- a/lib/models/Organization.js +++ b/lib/models/Organization.js @@ -73,6 +73,7 @@ const organizationSchema = new Schema({ }, ], visibleInSearch: Boolean, + tags: [], createdAt: { type: Date, default: Date.now, diff --git a/lib/models/Plugin.js b/lib/models/Plugin.js new file mode 100644 index 0000000000..aa6b00f76d --- /dev/null +++ b/lib/models/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 = mongoose.model('PluginTemp', pluginSchema); diff --git a/lib/models/Plugins.js b/lib/models/Plugins.js deleted file mode 100644 index 70ade9ca98..0000000000 --- a/lib/models/Plugins.js +++ /dev/null @@ -1,49 +0,0 @@ -const mongoose = require('mongoose'); -const Schema = mongoose.Schema; - -//this is the Structure of the Comments -const pluginSchema = new Schema({ - orgId: { - type: Schema.Types.ObjectId, - ref: 'Organization', - required: true, - }, - pluginName: { - type: String, - required: true, - }, - pluginKey: { - type: String, - required: false, - }, - pluginStatus: { - type: String, - required: true, - default: 'ACTIVE', - enum: ['ACTIVE', 'BLOCKED', 'DELETED'], - }, - pluginType: { - type: String, - required: true, - default: 'UNIVERSAL', - enum: ['UNIVERSAL', 'PRIVATE'], - }, - adminAccessAllowed: { - type: Boolean, - required: true, - default: true, - }, - additionalInfo: [ - { - type: Schema.Types.ObjectId, - ref: 'PluginField', - required: false, - }, - ], - createdAt: { - type: Date, - default: Date.now, - }, -}); - -module.exports = mongoose.model('Plugin', pluginSchema); diff --git a/lib/models/User.js b/lib/models/User.js index e2f644e9cd..161f3d8e96 100644 --- a/lib/models/User.js +++ b/lib/models/User.js @@ -106,6 +106,14 @@ const userSchema = new Schema({ required: true, default: true, }, + adminApproved: { + type: Boolean, + default: false, + }, + createdAt: { + type: Date, + default: Date.now, + }, }); userSchema.plugin(mongoosePaginate); diff --git a/lib/resolvers/Mutation.js b/lib/resolvers/Mutation.js index 33e1d82194..65e6b079b0 100644 --- a/lib/resolvers/Mutation.js +++ b/lib/resolvers/Mutation.js @@ -1,5 +1,8 @@ const signUp = require('./auth_mutations/signup'); const login = require('./auth_mutations/login'); +const otp = require('./auth_mutations/otp'); +const recaptcha = require('./auth_mutations/recaptcha'); +const forgotPassword = require('./auth_mutations/forgotPassword'); const saveFcmToken = require('./auth_mutations/saveFcmToken'); const logout = require('./auth_mutations/logout'); const refreshToken = require('./auth_mutations/refresh_token'); @@ -17,6 +20,7 @@ const joinPublicOrganization = require('./member_mutations/join_public_organizat const leaveOrganization = require('./member_mutations/leave_organization'); const removeMember = require('./member_mutations/removeMember'); const updateUserProfile = require('./user_mutations/updateUserProfile'); +const updateUserType = require('./user_mutations/updateUserType'); const registerForEvent = require('./event_mutations/registerForEvent'); const unregisterForEventByUser = require('./event_mutations/unregisterForEvent'); // const createEventProject = require("./event_project_mutations/createProject") @@ -30,6 +34,8 @@ const adminRemovePost = require('./admin_mutations/admin-remove-post'); const adminRemoveEvent = require('./admin_mutations/admin-remove-event'); const adminRemoveGroup = require('./admin_mutations/admin-remove-group-chat'); +const { acceptAdmin, rejectAdmin } = require('./admin_mutations/adminRequest'); + const createPost = require('./post_mutations/createPost'); const removePost = require('./post_mutations/removePost'); const createComment = require('./post_mutations/createComment'); @@ -63,13 +69,19 @@ const removeUserFromGroupChat = require('./group_chat_mutations/removeUserFromGr const updateLanguage = require('./language_mutation/updateLanguage'); const blockPluginCreationBySuperadmin = require('../resolvers/user_mutations/blockForPlugin'); -const createPlugin = require('./plugin_mutations/createPlugin'); const createMessageChat = require('./message_chat_mutation/createMessageChat'); const addLanguageTranslation = require('./language_maintainer_mutation/addLanguageTranslation'); +const createPlugin = require('./plugin_mutations/createPlugin'); +const updatePluginStatus = require('./plugin_mutations/updatePluginStatus'); +const updatePluginInstalledOrgs = require('./plugin_mutations/updatePluginInstalledOrgs'); + const Mutation = { signUp, login, + otp, + recaptcha, + forgotPassword, saveFcmToken, logout, refreshToken, @@ -77,6 +89,7 @@ const Mutation = { updateLanguage, updateUserProfile, + updateUserType, createOrganization, createEvent, @@ -85,6 +98,9 @@ const Mutation = { updateEvent, unregisterForEventByUser, + acceptAdmin, + rejectAdmin, + createAdmin, removeAdmin, updateOrganization, @@ -135,10 +151,13 @@ const Mutation = { addUserToGroupChat, removeUserFromGroupChat, blockPluginCreationBySuperadmin, - createPlugin, createMessageChat, addLanguageTranslation, + + createPlugin, + updatePluginStatus, + updatePluginInstalledOrgs, }; module.exports = Mutation; diff --git a/lib/resolvers/Query.js b/lib/resolvers/Query.js index 688c39b1ea..9712cafae1 100644 --- a/lib/resolvers/Query.js +++ b/lib/resolvers/Query.js @@ -22,15 +22,13 @@ const postsByOrganizationConnection = require('../resolvers/post_organization_qu const { users, user, me } = require('./user_query/users'); const { usersConnection } = require('./user_query/users'); const { organizationsMemberConnection } = require('./user_query/users'); -const plugin = require('./plugin_query/super-admin-plugin-query'); -const adminPlugin = require('./plugin_query/admin-plugin-query'); const myLanguage = require('../resolvers/user_query/myLanguage'); const userLanguage = require('../resolvers/user_query/userLanguage'); const getlanguage = require('../resolvers/language_maintainer_query/getlanguage'); const directChatsByUserID = require('./direct_chat_query/directChatsByUserID'); const directChatsMessagesByChatID = require('./direct_chat_query/directChatsMessagesByChatID'); const checkAuth = require('./auth_query/checkAuth'); - +const getPlugins = require('./plugin_query/getPlugins'); const Query = { me, user, @@ -69,10 +67,9 @@ const Query = { myLanguage, userLanguage, - plugin, - adminPlugin, getlanguage, + getPlugins, }; module.exports = Query; diff --git a/lib/resolvers/admin_mutations/adminRequest.js b/lib/resolvers/admin_mutations/adminRequest.js new file mode 100644 index 0000000000..4eddd4d7f4 --- /dev/null +++ b/lib/resolvers/admin_mutations/adminRequest.js @@ -0,0 +1,37 @@ +const { USER_NOT_AUTHORIZED } = require('../../../constants'); +const userExists = require('../../helper_functions/userExists'); +const User = require('../../models/User'); + +const acceptAdmin = async (parent, args, context) => { + const { id } = args; + + const isSuperAdmin = await userExists(context.userId); + + if (isSuperAdmin.userType !== 'SUPERADMIN') { + throw new Error(USER_NOT_AUTHORIZED); + } + + await userExists(id); + + await User.findByIdAndUpdate({ _id: id }, { adminApproved: true }); + + return true; +}; + +const rejectAdmin = async (parent, args, context) => { + const { id } = args; + + const isSuperAdmin = await userExists(context.userId); + + if (isSuperAdmin.userType !== 'SUPERADMIN') { + throw new Error(USER_NOT_AUTHORIZED); + } + + await userExists(id); + + await User.findByIdAndDelete({ _id: id }); + + return true; +}; + +module.exports = { acceptAdmin, rejectAdmin }; diff --git a/lib/resolvers/auth_mutations/forgotPassword.js b/lib/resolvers/auth_mutations/forgotPassword.js new file mode 100644 index 0000000000..2da3aabd8f --- /dev/null +++ b/lib/resolvers/auth_mutations/forgotPassword.js @@ -0,0 +1,31 @@ +const bcrypt = require('bcryptjs'); +const jwt_decode = require('jwt-decode'); + +const { INVALID_OTP } = require('../../../constants'); + +const User = require('../../models/User'); + +module.exports = async (parent, args) => { + const { userOtp, newPassword, otpToken } = args.data; + + const { email, otp } = jwt_decode(otpToken); + + const isOtpValid = await bcrypt.compare(userOtp, otp); + + if (!isOtpValid) { + throw new Error(INVALID_OTP); + } + + const hashedPassword = await bcrypt.hash(newPassword, 12); + + const isChanges = await User.findOneAndUpdate( + { email }, + { password: hashedPassword } + ); + + if (isChanges) { + return true; + } + + return false; +}; diff --git a/lib/resolvers/auth_mutations/logout.js b/lib/resolvers/auth_mutations/logout.js index da2df57bcb..217fde2210 100644 --- a/lib/resolvers/auth_mutations/logout.js +++ b/lib/resolvers/auth_mutations/logout.js @@ -5,13 +5,17 @@ const { USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_CODE, USER_NOT_FOUND_PARAM, + USER_NOT_FOUND, + IN_PRODUCTION, } = require('../../../constants'); module.exports = async (parent, args, context) => { const user = await User.findOne({ _id: context.userId }); if (!user) { throw new NotFoundError( - requestContext.translate(USER_NOT_FOUND_MESSAGE), + !IN_PRODUCTION + ? USER_NOT_FOUND + : requestContext.translate(USER_NOT_FOUND_MESSAGE), USER_NOT_FOUND_CODE, USER_NOT_FOUND_PARAM ); diff --git a/lib/resolvers/auth_mutations/otp.js b/lib/resolvers/auth_mutations/otp.js new file mode 100644 index 0000000000..618448af79 --- /dev/null +++ b/lib/resolvers/auth_mutations/otp.js @@ -0,0 +1,38 @@ +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); + +const User = require('../../models/User'); +const mailer = require('../../helper_functions/mailer'); +const { USER_NOT_FOUND } = require('../../../constants'); + +module.exports = async (parent, args) => { + const { email } = args.data; + + const user = await User.findOne({ email }); + + if (!user) { + throw new Error(USER_NOT_FOUND); + } + + const username = `${user.firstName} ${user.lastName}`; + + const otp = (Math.floor(Math.random() * 10000) + 9999).toString(); + + const hashedOtp = await bcrypt.hash(otp, 10); + + const otpToken = jwt.sign( + { email, otp: hashedOtp }, + process.env.ACCESS_TOKEN_SECRET, + { + expiresIn: '15m', + } + ); + + const subject = 'OTP for Talawa-admin forgot password'; + const body = `

Hi, ${username}

Your OTP: ${otp}

Your OTP will expires in 5 minutes.



Do not share your otp with others.`; + + return mailer(email, subject, body).then((info) => { + console.log(info); + return { otpToken }; + }); +}; diff --git a/lib/resolvers/auth_mutations/recaptcha.js b/lib/resolvers/auth_mutations/recaptcha.js new file mode 100644 index 0000000000..8451d6548d --- /dev/null +++ b/lib/resolvers/auth_mutations/recaptcha.js @@ -0,0 +1,11 @@ +const axios = require('axios'); + +module.exports = async (parent, args) => { + const { recaptchaToken } = args.data; + + const response = await axios.post( + `https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${recaptchaToken}` + ); + + return response.data.success; +}; diff --git a/lib/resolvers/auth_mutations/saveFcmToken.js b/lib/resolvers/auth_mutations/saveFcmToken.js index a6b66559cf..b1a04cf451 100644 --- a/lib/resolvers/auth_mutations/saveFcmToken.js +++ b/lib/resolvers/auth_mutations/saveFcmToken.js @@ -5,13 +5,17 @@ const { USER_NOT_FOUND_MESSAGE, USER_NOT_FOUND_CODE, USER_NOT_FOUND_PARAM, + USER_NOT_FOUND, + IN_PRODUCTION, } = require('../../../constants'); module.exports = async (parent, args, context) => { const user = await User.findById(context.userId); if (!user) { throw new NotFoundError( - requestContext.translate(USER_NOT_FOUND_MESSAGE), + !IN_PRODUCTION + ? USER_NOT_FOUND + : requestContext.translate(USER_NOT_FOUND_MESSAGE), USER_NOT_FOUND_CODE, USER_NOT_FOUND_PARAM ); diff --git a/lib/resolvers/plugin_mutations/createPlugin.js b/lib/resolvers/plugin_mutations/createPlugin.js index b0c217b9c8..37f4c6a072 100644 --- a/lib/resolvers/plugin_mutations/createPlugin.js +++ b/lib/resolvers/plugin_mutations/createPlugin.js @@ -1,71 +1,23 @@ -const User = require('../../models/User'); -const Plugin = require('../../models/Plugins'); -const PluginField = require('../../models/PluginsField'); -const Organization = require('../../models/Organization'); - -const { UnauthorizedError } = require('errors'); -const { NotFoundError } = require('errors'); -const requestContext = require('talawa-request-context'); - +const Plugin = require('../../models/Plugin'); +/** + * @name createPlugin creates a Plugin and return the same + * @description creates a document of Plugin type and stores it in database + * @param {any} parent parent of current request + * @param {object} args payload provided with the request + * @param {any} context context of entire application + */ +// eslint-disable-next-line no-unused-vars module.exports = async (parent, args, context) => { - let org = await Organization.findOne({ - _id: args.plugin.orgId, - }); - - if (!org) { - throw new NotFoundError( - requestContext.translate('organization.notFound'), - 'organization.notFound', - 'organization' - ); - } - - const user = await User.findOne({ - _id: context.userId, - pluginCreationAllowed: true, - }); - - if (!user) { - throw new NotFoundError( - requestContext.translate('user.notFound'), - 'user.notFound', - 'user' - ); - } - - const isSuperAdmin = user.userType === 'SUPERADMIN'; - if (!isSuperAdmin) { - const isAdmin = org.admins.includes(user.id); - if (!isAdmin) { - throw new UnauthorizedError( - requestContext.translate('user.notAuthorized'), - 'user.notAuthorized', - 'userAuthorization' - ); - } - } - - let pluginAddnFields = []; - if (args.plugin.fields.length > 0) { - for (let i = 0; i < args.plugin.fields.length; i++) { - let pluginField = new PluginField({ - key: args.plugin.fields[i].key, - value: args.plugin.fields[i].value, - }); - - pluginAddnFields.push(pluginField); - } - } - + //create MongoDB document let plugin = new Plugin({ - orgId: args.plugin.orgId, - pluginName: args.plugin.pluginName, - pluginKey: args.plugin.pluginKey, - additionalInfo: pluginAddnFields, + pluginName: args.pluginName, + pluginCreatedBy: args.pluginCreatedBy, + pluginDesc: args.pluginDesc, + pluginInstallStatus: args.pluginInstallStatus, + installedOrgs: args.installedOrgs, }); - - plugin = await plugin.save(); - + //store the plugin + plugin = await Plugin.save(); return { ...plugin._doc, }; diff --git a/lib/resolvers/plugin_mutations/updatePluginInstalledOrgs.js b/lib/resolvers/plugin_mutations/updatePluginInstalledOrgs.js new file mode 100644 index 0000000000..ac93a35e98 --- /dev/null +++ b/lib/resolvers/plugin_mutations/updatePluginInstalledOrgs.js @@ -0,0 +1,47 @@ +const Plugin = require('../../models/Plugin'); +/** + * @name updatePluginInstalledOrgs + * @description updates the installedOrgs list of the specific plugin and adds or removes the current orgId from the list. + * @param {any} parent parent of current request + * @param {object} args payload provided with the request + * @param {any} context context of entire application + */ +// eslint-disable-next-line no-unused-vars +module.exports = async (parent, args, context) => { + let plug = await Plugin.findById(args.id); + const plugOrgList = plug?.installedOrgs; + const isDuplicate = plugOrgList?.includes(args.orgId); + // remove the entry if duplicate + if (isDuplicate) { + // eslint-disable-next-line no-unused-vars + const result = await Plugin.findByIdAndUpdate( + args.id, + { $pull: { installedOrgs: args.orgId } }, + { new: true }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log('Updated Plugin with installed orgs : ', res); + } + } + ); + } else { + // eslint-disable-next-line no-unused-vars + const result = await Plugin.findByIdAndUpdate( + args.id, + { $push: { installedOrgs: args.orgId } }, + { new: true }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log('Updated Plugin with installed orgs : ', res); + } + } + ); + } + + plug = await Plugin.findById(args.id); + return plug; +}; diff --git a/lib/resolvers/plugin_mutations/updatePluginStatus.js b/lib/resolvers/plugin_mutations/updatePluginStatus.js new file mode 100644 index 0000000000..6151913241 --- /dev/null +++ b/lib/resolvers/plugin_mutations/updatePluginStatus.js @@ -0,0 +1,27 @@ +const Plugin = require('../../models/Plugin'); +/** + * @name updatePluginStatus + * @description toggles the installStatus of the plugin + * @param {any} parent parent of current request + * @param {object} args payload provided with the request + * @param {any} context context of entire application + */ +// eslint-disable-next-line no-unused-vars +module.exports = async (parent, args, context) => { + console.log('Argment s ', args); + // eslint-disable-next-line no-unused-vars + const result = await Plugin.findByIdAndUpdate( + args.id, + { pluginInstallStatus: args.status }, + { new: true }, + (err, res) => { + if (err) { + console.log(err); + } else { + console.log('Updated Plugin : ', res); + } + } + ); + const plug = await Plugin.findById(args.id); + return plug; +}; diff --git a/lib/resolvers/plugin_query/admin-plugin-query.js b/lib/resolvers/plugin_query/admin-plugin-query.js deleted file mode 100644 index 8252957b08..0000000000 --- a/lib/resolvers/plugin_query/admin-plugin-query.js +++ /dev/null @@ -1,29 +0,0 @@ -const Plugin = require('../../models/Plugins'); -const adminCheck = require('../functions/adminCheck'); -const Organization = require('../../models/Organization'); - -const { NotFoundError } = require('errors'); -const requestContext = require('talawa-request-context'); - -module.exports = async (parent, args, context) => { - const organizationFound = await Organization.findOne({ - _id: args.orgId, - }); - - if (!organizationFound) { - throw new NotFoundError( - requestContext.translate('organization.notFound'), - 'organization.notFound', - 'organization' - ); - } - - adminCheck(context, organizationFound); - const pluginFound = await Plugin.find({ - orgId: args.orgId, - pluginStatus: 'ACTIVE', - adminAccessAllowed: true, - }); - - return pluginFound; -}; diff --git a/lib/resolvers/plugin_query/getPlugins.js b/lib/resolvers/plugin_query/getPlugins.js new file mode 100644 index 0000000000..c4c0245cf1 --- /dev/null +++ b/lib/resolvers/plugin_query/getPlugins.js @@ -0,0 +1,8 @@ +const Plugin = require('../../models/Plugin'); +/** + * @name getPlugins a GraphQL Query + * @description returns list of plugin from database + */ +module.exports = async () => { + return await Plugin.find(); +}; diff --git a/lib/resolvers/plugin_query/super-admin-plugin-query.js b/lib/resolvers/plugin_query/super-admin-plugin-query.js deleted file mode 100644 index c1c4eca5a6..0000000000 --- a/lib/resolvers/plugin_query/super-admin-plugin-query.js +++ /dev/null @@ -1,42 +0,0 @@ -const Plugin = require('../../models/Plugins'); -const User = require('../../models/User'); -const superAdminCheck = require('../functions/superAdminCheck'); -const Organization = require('../../models/Organization'); - -const { NotFoundError } = require('errors'); -const requestContext = require('talawa-request-context'); - -module.exports = async (parent, args, context) => { - const organizationFound = await Organization.findOne({ - _id: args.orgId, - }); - - if (!organizationFound) { - throw new NotFoundError( - requestContext.translate('organization.notFound'), - 'organization.notFound', - 'organization' - ); - } - - const user = await User.findOne({ - _id: context.userId, - }); - - if (!user) { - throw new NotFoundError( - requestContext.translate('user.notFound'), - 'user.notFound', - 'user' - ); - } - - superAdminCheck(context, user); - - const pluginFound = await Plugin.find({ - orgId: args.orgId, - pluginStatus: 'ACTIVE', - }); - - return pluginFound; -}; diff --git a/lib/resolvers/user_mutations/updateUserType.js b/lib/resolvers/user_mutations/updateUserType.js new file mode 100644 index 0000000000..4866db59ed --- /dev/null +++ b/lib/resolvers/user_mutations/updateUserType.js @@ -0,0 +1,19 @@ +const User = require('../../models/User'); +const userExists = require('../../helper_functions/userExists'); +const { USER_NOT_AUTHORIZED } = require('../../../constants'); + +module.exports = async (parent, args, context) => { + const { id, userType } = args.data; + + const isSuperAdmin = await userExists(context.userId); + + if (isSuperAdmin.userType !== 'SUPERADMIN') { + throw new Error(USER_NOT_AUTHORIZED); + } + + await userExists(id); + + await User.findByIdAndUpdate({ _id: id }, { userType, adminApproved: true }); + + return true; +}; diff --git a/lib/schema/mutation.graphql b/lib/schema/mutation.graphql index 8d806930a6..b1e619861d 100644 --- a/lib/schema/mutation.graphql +++ b/lib/schema/mutation.graphql @@ -3,6 +3,9 @@ module.exports = ` type Mutation { signUp(data: UserInput!, file:Upload): AuthData! login(data: LoginInput!): AuthData! + otp(data: OTPInput!): OtpData! + recaptcha(data: RecaptchaVerification!): Boolean! + forgotPassword(data: ForgotPasswordData!): Boolean! saveFcmToken(token: String) : Boolean! @auth refreshToken(refreshToken: String!) : ExtendSession! revokeRefreshTokenForUser(userId: String!) : Boolean! @@ -10,6 +13,7 @@ module.exports = ` logout: Boolean! @auth updateUserProfile(data: UpdateUserInput, file:Upload): User! @auth + updateUserType(data: UpdateUserTypeInput!): Boolean! @auth createEvent(data: EventInput): Event! @auth removeEvent(id: ID!): Event! @auth registerForEvent(id: ID!): Event! @auth @@ -20,6 +24,9 @@ module.exports = ` updateOrganization(id:ID!, data: UpdateOrganizationInput) : Organization! @auth removeOrganization(id: ID!) : User! @auth + acceptAdmin(id: ID!): Boolean! @auth + rejectAdmin(id: ID!): Boolean! @auth + createAdmin (data: UserAndOrganizationInput!) : User! @auth removeAdmin (data: UserAndOrganizationInput!) : User! @auth joinPublicOrganization (organizationId: ID!) : User! @auth @@ -69,10 +76,13 @@ module.exports = ` addUserToGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth removeUserFromGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth blockPluginCreationBySuperadmin(userId: ID!, blockUser: Boolean!): User! @auth - createPlugin(plugin: PluginInput!): Plugin! @auth createMessageChat(data:MessageChatInput!): MessageChat! @auth addLanguageTranslation(data: LanguageInput!): Language! @auth + + createPlugin(pluginName: String! , pluginCreatedBy: String! ,pluginDesc: String! ,pluginInstallStatus: Boolean!, installedOrgs:[ID!] ):Plugin! + updatePluginStatus(id: ID!, status: Boolean!) : Plugin! + updatePluginInstalledOrgs(id: ID! ,orgId: ID!) :Plugin! } ` diff --git a/lib/schema/organization/organization.graphql b/lib/schema/organization/organization.graphql index e65640ac26..59615df6e2 100644 --- a/lib/schema/organization/organization.graphql +++ b/lib/schema/organization/organization.graphql @@ -19,6 +19,7 @@ module.exports = ` visibleInSearch: Boolean! apiUrl:String! createdAt:String + tags: [String] } type OrganizationInfoNode { @@ -40,6 +41,7 @@ module.exports = ` isPublic: Boolean! visibleInSearch: Boolean! apiUrl:String + tags: [String] } diff --git a/lib/schema/plugin/plugin.graphql b/lib/schema/plugin/plugin.graphql index 90dba979b4..c64d18475c 100644 --- a/lib/schema/plugin/plugin.graphql +++ b/lib/schema/plugin/plugin.graphql @@ -1,13 +1,13 @@ module.exports = ` - type Plugin { - orgId: Organization! - pluginName: String! - pluginKey: String - pluginStatus: Status! - pluginType: Type! - additionalInfo: [PluginField!] - createdAt: String - } + # type Plugin { + # orgId: Organization! + # pluginName: String! + # pluginKey: String + # pluginStatus: Status! + # pluginType: Type! + # additionalInfo: [PluginField!] + # createdAt: String + # } input PluginInput{ orgId: ID! @@ -17,9 +17,19 @@ module.exports = ` fields: [PluginFieldInput] } - input PluginFieldInput{ + input PluginFieldInput{ key: String! - value: String! + value: String! + } + + # For Plugins + type Plugin { + _id: ID! + pluginName: String! + pluginCreatedBy: String! + pluginDesc: String! + pluginInstallStatus: Boolean! + installedOrgs : [ID!]! } ` \ No newline at end of file diff --git a/lib/schema/query.graphql b/lib/schema/query.graphql index 925160609c..aebe0a876f 100644 --- a/lib/schema/query.graphql +++ b/lib/schema/query.graphql @@ -43,5 +43,8 @@ module.exports = ` plugin(orgId: ID!): [Plugin] adminPlugin(orgId: ID!): [Plugin] getlanguage(lang_code: String!): [Translation] + + # For Plugins + getPlugins: [Plugin] } ` \ No newline at end of file diff --git a/lib/schema/user/auth.graphql b/lib/schema/user/auth.graphql index 9d22f453c2..e0a8920ab3 100644 --- a/lib/schema/user/auth.graphql +++ b/lib/schema/user/auth.graphql @@ -39,5 +39,21 @@ module.exports = ` refreshToken: String! } + input RecaptchaVerification { + recaptchaToken: String! + } + + input OTPInput { + email: String! + } + type OtpData { + otpToken: String! + } + + input ForgotPasswordData { + userOtp: String! + newPassword: String! + otpToken: String! + } ` \ No newline at end of file diff --git a/lib/schema/user/user.graphql b/lib/schema/user/user.graphql index ebb49914e0..d2a91a6554 100644 --- a/lib/schema/user/user.graphql +++ b/lib/schema/user/user.graphql @@ -19,6 +19,8 @@ module.exports = ` image: String organizationUserBelongsTo: Organization pluginCreationAllowed: Boolean + adminApproved: Boolean + createdAt:String } type UserConnection { @@ -47,6 +49,11 @@ module.exports = ` email: String } + input UpdateUserTypeInput { + userType: String + id: ID + } + input UserWhereInput { id: ID id_not: ID diff --git a/locales/README.md b/locales/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package-lock.json b/package-lock.json index 8937a78234..15a7c3859a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "image-hash": "^4.0.1", "inquirer": "^8.2.4", "jsonwebtoken": "^8.5.1", + "jwt-decode": "^3.1.2", "logger": "file:lib/helper_lib/logger", "markdown-js": "^0.0.4", "marked": "^2.0.1", @@ -47,6 +48,7 @@ "morgan": "^1.10.0", "netmask": ">=2.0.1", "node-cmd": "^5.0.0", + "nodemailer": "^6.7.5", "pm2": "^5.1.2", "request-tracing": "file:lib/helper_lib/request-tracing", "shortid": "^2.2.16", @@ -8727,6 +8729,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -9083,14 +9090,14 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.1.tgz", + "integrity": "sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw==", "bin": { "marked": "bin/marked" }, "engines": { - "node": ">= 10" + "node": ">= 8.16.2" } }, "node_modules/marked-terminal": { @@ -9184,7 +9191,7 @@ "version": "1.7.8", "resolved": "https://registry.npmjs.org/merge-graphql-schemas/-/merge-graphql-schemas-1.7.8.tgz", "integrity": "sha512-C3EJ1i86OjmbcCT524wVPRl17M5VZzgyh9kIGYAlYnAILX+7xfh8cCbMKfehh9n4opZg6CtcPogCiVZ6PB2NyQ==", - "deprecated": "Merge GraphQL Schemas has been deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-merge-graphql-schemas for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.", + "deprecated": "Merge GraphQL Schemas has been deprecated and merged into GraphQL Tools, so it will no longer get updates. Use GraphQL Tools instead to stay up-to-date! Check out https://www.graphql-tools.com/docs/migration-from-merge-graphql-schemas for migration and https://the-guild.dev/blog/graphql-tools-v6 for new changes.", "dev": true, "dependencies": { "@graphql-toolkit/file-loading": "0.10.4", @@ -9664,6 +9671,14 @@ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz", + "integrity": "sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -14590,8 +14605,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==" } } }, @@ -15251,8 +15265,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", @@ -15589,8 +15602,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", @@ -15660,8 +15672,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", @@ -17337,8 +17348,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", @@ -18335,8 +18345,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", @@ -19311,8 +19320,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", @@ -19837,6 +19845,11 @@ "safe-buffer": "^5.0.1" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -20150,9 +20163,9 @@ } }, "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.1.tgz", + "integrity": "sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw==" }, "marked-terminal": { "version": "5.1.1", @@ -20409,8 +20422,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", @@ -20577,6 +20589,11 @@ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, + "nodemailer": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz", + "integrity": "sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==" + }, "nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -23095,8 +23112,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/package.json b/package.json index 62eb6eae9a..b8425b9e3b 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "image-hash": "^4.0.1", "inquirer": "^8.2.4", "jsonwebtoken": "^8.5.1", + "jwt-decode": "^3.1.2", "logger": "file:lib/helper_lib/logger", "markdown-js": "^0.0.4", "marked": "^2.0.1", @@ -69,6 +70,7 @@ "morgan": "^1.10.0", "netmask": ">=2.0.1", "node-cmd": "^5.0.0", + "nodemailer": "^6.7.5", "pm2": "^5.1.2", "request-tracing": "file:lib/helper_lib/request-tracing", "shortid": "^2.2.16", diff --git a/tests/database/mongoImplementation.spec.js b/tests/database/mongoImplementation.spec.js index 348c6c3267..dd4bf98bc7 100644 --- a/tests/database/mongoImplementation.spec.js +++ b/tests/database/mongoImplementation.spec.js @@ -1,5 +1,6 @@ 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'; @@ -29,6 +30,13 @@ afterAll(async () => { }); 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', diff --git a/tests/database/multi-tenancy.spec.js b/tests/database/multi-tenancy.spec.js index 1e322983f8..43913e3a0d 100644 --- a/tests/database/multi-tenancy.spec.js +++ b/tests/database/multi-tenancy.spec.js @@ -1,6 +1,13 @@ const shortid = require('shortid'); const Tenant = require('../../lib/models/Tenant'); const connectionManager = require('../../lib/ConnectionManager'); +const { + Types: { ObjectId }, +} = require('mongoose'); +const { + CONNECTION_NOT_FOUND, + ORGANIZATION_NOT_FOUND, +} = require('../../constants'); const database = require('../../db'); const getUserIdFromSignUp = require('../functions/getUserIdFromSignup'); @@ -73,16 +80,20 @@ afterAll(async () => { describe('tenant is working and transparent from main db', () => { test('initTenants and destroyConnections', async () => { - let conn = connectionManager.getTenantConnection(organizationId); - expect(conn).toBe(null); + let conn; + expect(() => { + connectionManager.getTenantConnection(organizationId); + }).toThrow(CONNECTION_NOT_FOUND); await connectionManager.initTenants(); conn = connectionManager.getTenantConnection(organizationId); expect(conn).toBeTruthy(); await connectionManager.destroyConnections(); - conn = connectionManager.getTenantConnection(organizationId); - expect(conn).toBe(null); + expect(() => { + connectionManager.getTenantConnection(organizationId); + }).toThrow(CONNECTION_NOT_FOUND); await connectionManager.initTenants(); }); + test('addConnection', async () => { const organization = new Organization({ name: 'second tenant organization', @@ -120,11 +131,19 @@ describe('tenant is working and transparent from main db', () => { 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 = connectionManager.getTenantConnection(organizationId); const newPost = new conn.Post({ diff --git a/tests/resolvers/admin_mutations/adminRequest.spec.js b/tests/resolvers/admin_mutations/adminRequest.spec.js new file mode 100644 index 0000000000..e73f736a12 --- /dev/null +++ b/tests/resolvers/admin_mutations/adminRequest.spec.js @@ -0,0 +1,124 @@ +const mongoose = require('mongoose'); +const shortid = require('shortid'); +const { USER_NOT_AUTHORIZED, USER_NOT_FOUND } = require('../../../constants'); + +const database = require('../../../db'); +const User = require('../../../lib/models/User'); +const { + acceptAdmin, + rejectAdmin, +} = require('../../../lib/resolvers/admin_mutations/adminRequest'); +const getUserId = require('../../functions/getUserIdFromSignup'); + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing admin request resolver', () => { + test('Testing accept request', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const args = { + id: userId, + }; + + const response = await acceptAdmin({}, args, { + userId, + }); + + expect(response).toBeTruthy(); + }); + + test('Testing, when the loggedIn user is not SUPERADMIN in accept admin', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + const args = { + id: userId, + }; + + await expect(async () => { + await acceptAdmin({}, args, { + userId, + }); + }).rejects.toEqual(Error(USER_NOT_AUTHORIZED)); + }); + + test('Testing, when user is not found in accept admin', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const context = { + userId, + }; + + const args = { + id: mongoose.Types.ObjectId(), + }; + + await expect(async () => { + await acceptAdmin({}, args, context); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); + + test('Testing reject request', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const args = { + id: userId, + }; + + const response = await rejectAdmin({}, args, { + userId, + }); + + expect(response).toBeTruthy(); + }); + + test('Testing, when the loggedIn user is not SUPERADMIN in reject admin', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + const args = { + id: userId, + }; + + await expect(async () => { + await rejectAdmin({}, args, { + userId, + }); + }).rejects.toEqual(Error(USER_NOT_AUTHORIZED)); + }); + + test('Testing, when user is not found in reject admin', async () => { + const generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + const userId = await getUserId(generatedEmail); + + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const context = { + userId, + }; + + const args = { + id: mongoose.Types.ObjectId(), + }; + + await expect(async () => { + await rejectAdmin({}, args, context); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); +}); diff --git a/tests/resolvers/auth_mutations/forgotPassword.spec.js b/tests/resolvers/auth_mutations/forgotPassword.spec.js new file mode 100644 index 0000000000..b41d1c9eca --- /dev/null +++ b/tests/resolvers/auth_mutations/forgotPassword.spec.js @@ -0,0 +1,84 @@ +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const shortid = require('shortid'); +const { INVALID_OTP } = require('../../../constants'); + +const database = require('../../../db'); +const forgotPassword = require('../../../lib/resolvers/auth_mutations/forgotPassword'); +const getToken = require('../../functions/getToken'); + +let hashedOtp; +let otpToken; +let token; + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); + + let generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + token = await getToken(generatedEmail); + hashedOtp = await bcrypt.hash('12345', 10); + otpToken = jwt.sign( + { email: generatedEmail, otp: hashedOtp }, + process.env.ACCESS_TOKEN_SECRET, + { + expiresIn: '15m', + } + ); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing forgotPassword resolver', () => { + test('forgotPassword', async () => { + console.log(token); + + const args = { + data: { + userOtp: '12345', + newPassword: 'newPassword', + otpToken: otpToken, + }, + }; + + const response = await forgotPassword({}, args); + expect(response).toBeTruthy(); + }); + + test('Testing, when user otp is incorrect', async () => { + const args = { + data: { + userOtp: '54321', + newPassword: 'newPassword', + otpToken: otpToken, + }, + }; + + await expect(async () => { + await forgotPassword({}, args); + }).rejects.toEqual(Error(INVALID_OTP)); + }); + + test('Testing, when user email is incorrect', async () => { + const fakeOtpToken = jwt.sign( + { email: 'abc321@email.com', otp: hashedOtp }, + process.env.ACCESS_TOKEN_SECRET, + { + expiresIn: '15m', + } + ); + + const args = { + data: { + userOtp: '12345', + newPassword: 'newPassword', + otpToken: fakeOtpToken, + }, + }; + + const response = await forgotPassword({}, args); + expect(response).toBeFalsy(); + }); +}); diff --git a/tests/resolvers/auth_mutations/logout.spec.js b/tests/resolvers/auth_mutations/logout.spec.js new file mode 100644 index 0000000000..97191d6685 --- /dev/null +++ b/tests/resolvers/auth_mutations/logout.spec.js @@ -0,0 +1,46 @@ +const mongoose = require('mongoose'); +const shortid = require('shortid'); +const { USER_NOT_FOUND } = require('../../../constants'); + +const database = require('../../../db'); +const logout = require('../../../lib/resolvers/auth_mutations/logout'); +const getToken = require('../../functions/getToken'); +const getUserId = require('../../functions/getUserId'); + +let generatedEmail; +let userId; + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); + + generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + await getToken(generatedEmail); + userId = await getUserId(generatedEmail); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing logout resolver', () => { + test('Logout user', async () => { + const context = { + userId, + }; + + const response = await logout({}, {}, context); + + expect(response).toBeTruthy(); + }); + + test('Testing, when user is not found', async () => { + const context = { + userId: mongoose.Types.ObjectId(), + }; + + await expect(async () => { + await logout({}, {}, context); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); +}); diff --git a/tests/resolvers/auth_mutations/otp.spec.js b/tests/resolvers/auth_mutations/otp.spec.js new file mode 100644 index 0000000000..facb020998 --- /dev/null +++ b/tests/resolvers/auth_mutations/otp.spec.js @@ -0,0 +1,47 @@ +const shortid = require('shortid'); +const { USER_NOT_FOUND, ERROR_IN_SENDING_MAIL } = require('../../../constants'); + +const database = require('../../../db'); +const otp = require('../../../lib/resolvers/auth_mutations/otp'); +const getToken = require('../../functions/getToken'); + +let generatedEmail; +let token; + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); + + generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + token = await getToken(generatedEmail); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing otp resolver', () => { + test('otp', async () => { + console.log(token); + + const args = { + data: { + email: generatedEmail, + }, + }; + + await expect(() => otp({}, args)).rejects.toEqual(ERROR_IN_SENDING_MAIL); + }); + + test('Testing, when user email is incorrect', async () => { + const args = { + data: { + email: 'abc4321@email.com', + }, + }; + + await expect(async () => { + await otp({}, args); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); +}); diff --git a/tests/resolvers/auth_mutations/recaptcha.spec.js b/tests/resolvers/auth_mutations/recaptcha.spec.js new file mode 100644 index 0000000000..661782777f --- /dev/null +++ b/tests/resolvers/auth_mutations/recaptcha.spec.js @@ -0,0 +1,14 @@ +const recaptcha = require('../../../lib/resolvers/auth_mutations/recaptcha'); + +describe('Testing recaptcha resolver', () => { + test('recaptcha', async () => { + const args = { + data: { + recaptchaToken: 'dummyToken', + }, + }; + + const response = await recaptcha({}, args); + expect(response).toBeFalsy(); + }); +}); diff --git a/tests/resolvers/auth_mutations/saveFcmToken.spec.js b/tests/resolvers/auth_mutations/saveFcmToken.spec.js new file mode 100644 index 0000000000..4d8643206e --- /dev/null +++ b/tests/resolvers/auth_mutations/saveFcmToken.spec.js @@ -0,0 +1,46 @@ +const mongoose = require('mongoose'); +const shortid = require('shortid'); +const { USER_NOT_FOUND } = require('../../../constants'); + +const database = require('../../../db'); +const saveFcmToken = require('../../../lib/resolvers/auth_mutations/saveFcmToken'); +const getToken = require('../../functions/getToken'); +const getUserId = require('../../functions/getUserId'); + +let generatedEmail; +let userId; + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); + + generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + await getToken(generatedEmail); + userId = await getUserId(generatedEmail); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing save fcm resolver', () => { + test('Save Fcm Token', async () => { + const context = { + userId, + }; + + const response = await saveFcmToken({}, {}, context); + + expect(response).toBeTruthy(); + }); + + test('Testing, when user is not found', async () => { + const context = { + userId: mongoose.Types.ObjectId(), + }; + + await expect(async () => { + await saveFcmToken({}, {}, context); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); +}); diff --git a/tests/resolvers/mutation.spec.js b/tests/resolvers/mutation.spec.js new file mode 100644 index 0000000000..5a3f6ec0e8 --- /dev/null +++ b/tests/resolvers/mutation.spec.js @@ -0,0 +1,76 @@ +const Mutation = require('../../lib/resolvers/Mutation'); +describe('Test for Mutation', () => { + test('Mutation should have necessary properties', () => { + expect(Mutation).toHaveProperty('signUp'); + expect(Mutation).toHaveProperty('login'); + expect(Mutation).toHaveProperty('saveFcmToken'); + expect(Mutation).toHaveProperty('logout'); + expect(Mutation).toHaveProperty('refreshToken'); + expect(Mutation).toHaveProperty('revokeRefreshTokenForUser'); + expect(Mutation).toHaveProperty('updateLanguage'); + + expect(Mutation).toHaveProperty('updateUserProfile'); + expect(Mutation).toHaveProperty('createOrganization'); + + expect(Mutation).toHaveProperty('createEvent'); + expect(Mutation).toHaveProperty('registerForEvent'); + expect(Mutation).toHaveProperty('removeEvent'); + expect(Mutation).toHaveProperty('removeEvent'); + expect(Mutation).toHaveProperty('unregisterForEventByUser'); + + expect(Mutation).toHaveProperty('createAdmin'); + expect(Mutation).toHaveProperty('removeAdmin'); + expect(Mutation).toHaveProperty('updateOrganization'); + expect(Mutation).toHaveProperty('removeOrganization'); + expect(Mutation).toHaveProperty('joinPublicOrganization'); + expect(Mutation).toHaveProperty('leaveOrganization'); + expect(Mutation).toHaveProperty('removeMember'); + + expect(Mutation).toHaveProperty('adminRemovePost'); + expect(Mutation).toHaveProperty('adminRemoveGroup'); + expect(Mutation).toHaveProperty('adminRemoveEvent'); + + expect(Mutation).toHaveProperty('createPost'); + expect(Mutation).toHaveProperty('removePost'); + expect(Mutation).toHaveProperty('likePost'); + expect(Mutation).toHaveProperty('unlikePost'); + expect(Mutation).toHaveProperty('createTask'); + expect(Mutation).toHaveProperty('removeTask'); + expect(Mutation).toHaveProperty('updateTask'); + expect(Mutation).toHaveProperty('sendMembershipRequest'); + expect(Mutation).toHaveProperty('acceptMembershipRequest'); + expect(Mutation).toHaveProperty('rejectMembershipRequest'); + expect(Mutation).toHaveProperty('cancelMembershipRequest'); + + expect(Mutation).toHaveProperty('blockUser'); + expect(Mutation).toHaveProperty('unblockUser'); + + expect(Mutation).toHaveProperty('createComment'); + expect(Mutation).toHaveProperty('removeComment'); + expect(Mutation).toHaveProperty('likeComment'); + expect(Mutation).toHaveProperty('unlikeComment'); + + expect(Mutation).toHaveProperty('addUserImage'); + expect(Mutation).toHaveProperty('removeUserImage'); + expect(Mutation).toHaveProperty('addOrganizationImage'); + expect(Mutation).toHaveProperty('removeOrganizationImage'); + + expect(Mutation).toHaveProperty('removeOrganizationImage'); + expect(Mutation).toHaveProperty('removeDirectChat'); + expect(Mutation).toHaveProperty('sendMessageToDirectChat'); + + expect(Mutation).toHaveProperty('createGroupChat'); + expect(Mutation).toHaveProperty('removeGroupChat'); + expect(Mutation).toHaveProperty('sendMessageToGroupChat'); + expect(Mutation).toHaveProperty('addUserToGroupChat'); + expect(Mutation).toHaveProperty('removeUserFromGroupChat'); + expect(Mutation).toHaveProperty('blockPluginCreationBySuperadmin'); + + expect(Mutation).toHaveProperty('createMessageChat'); + expect(Mutation).toHaveProperty('addLanguageTranslation'); + + expect(Mutation).toHaveProperty('createPlugin'); + expect(Mutation).toHaveProperty('updatePluginStatus'); + expect(Mutation).toHaveProperty('updatePluginInstalledOrgs'); + }); +}); diff --git a/tests/resolvers/plugin_mutations/updatePluginInstallStatus.spec.js b/tests/resolvers/plugin_mutations/updatePluginInstallStatus.spec.js new file mode 100644 index 0000000000..24c4baf5fa --- /dev/null +++ b/tests/resolvers/plugin_mutations/updatePluginInstallStatus.spec.js @@ -0,0 +1,22 @@ +const database = require('../../../db'); +require('../../../lib/models/Plugin'); +const updatePluginInstalledOrgs = require('../../../lib/resolvers/plugin_mutations/updatePluginStatus'); +beforeAll(async () => { + require('dotenv').config(); // pull env variables from .env file + await database.connect(); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing updatePluginInstalledOrgs', () => { + test('Find existing post by post id', async () => { + const response = await updatePluginInstalledOrgs( + {}, + { id: '62cfcd6233bbe266f59644db', installedOrgs: [] }, + {} + ); + expect(response).toBeFalsy(); // result is null in github actions + }); +}); diff --git a/tests/resolvers/plugin_mutations/updatePluginInstalledOrgs.spec.js b/tests/resolvers/plugin_mutations/updatePluginInstalledOrgs.spec.js new file mode 100644 index 0000000000..320797d745 --- /dev/null +++ b/tests/resolvers/plugin_mutations/updatePluginInstalledOrgs.spec.js @@ -0,0 +1,22 @@ +const database = require('../../../db'); +require('../../../lib/models/Plugin'); +const updatePluginInstalledOrgs = require('../../../lib/resolvers/plugin_mutations/updatePluginInstalledOrgs'); +beforeAll(async () => { + require('dotenv').config(); // pull env variables from .env file + await database.connect(); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing updatePluginInstalledOrgs', () => { + test('Find existing post by post id', async () => { + const response = await updatePluginInstalledOrgs( + {}, + { id: '62cfcd6233bbe266f59644db', installedOrgs: [] }, + {} + ); + expect(response).toBeFalsy(); // response is null + }); +}); diff --git a/tests/resolvers/plugin_query/getPlugins.spec.js b/tests/resolvers/plugin_query/getPlugins.spec.js new file mode 100644 index 0000000000..a804878cce --- /dev/null +++ b/tests/resolvers/plugin_query/getPlugins.spec.js @@ -0,0 +1,18 @@ +const database = require('../../../db'); +require('../../../lib/models/Plugin'); +const getPlugins = require('../../../lib/resolvers/plugin_query/getPlugins'); +beforeAll(async () => { + require('dotenv').config(); // pull env variables from .env file + await database.connect(); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Post query testing for post resolver', () => { + test('Testing getPlugins Functions', async () => { + const response = await getPlugins(); + expect(response).toBeTruthy(); + }); +}); diff --git a/tests/resolvers/query.spec.js b/tests/resolvers/query.spec.js new file mode 100644 index 0000000000..c3c6948009 --- /dev/null +++ b/tests/resolvers/query.spec.js @@ -0,0 +1,45 @@ +const Query = require('../../lib/resolvers/Query'); +describe('Test from Query', () => { + test('Query should have necessary properties', () => { + expect(Query).toHaveProperty('me'); + expect(Query).toHaveProperty('user'); + expect(Query).toHaveProperty('users'); + expect(Query).toHaveProperty('usersConnection'); + + expect(Query).toHaveProperty('checkAuth'); + + expect(Query).toHaveProperty('organizations'); + expect(Query).toHaveProperty('organizationsConnection'); + expect(Query).toHaveProperty('organizationsMemberConnection'); + + expect(Query).toHaveProperty('isUserRegister'); + expect(Query).toHaveProperty('event'); + expect(Query).toHaveProperty('events'); + expect(Query).toHaveProperty('registrantsByEvent'); + expect(Query).toHaveProperty('eventsByOrganization'); + expect(Query).toHaveProperty('registeredEventsByUser'); + + expect(Query).toHaveProperty('groupChats'); + expect(Query).toHaveProperty('groupChatMessages'); + expect(Query).toHaveProperty('directChats'); + expect(Query).toHaveProperty('directChatMessages'); + expect(Query).toHaveProperty('directChatsByUserID'); + expect(Query).toHaveProperty('directChatsMessagesByChatID'); + + expect(Query).toHaveProperty('tasksByEvent'); + expect(Query).toHaveProperty('tasksByUser'); + expect(Query).toHaveProperty('comments'); + expect(Query).toHaveProperty('commentsByPost'); + expect(Query).toHaveProperty('post'); + expect(Query).toHaveProperty('posts'); + expect(Query).toHaveProperty('postsByOrganization'); + expect(Query).toHaveProperty('postsByOrganizationConnection'); + expect(Query).toHaveProperty('groups'); + + expect(Query).toHaveProperty('myLanguage'); + expect(Query).toHaveProperty('userLanguage'); + + expect(Query).toHaveProperty('getlanguage'); + expect(Query).toHaveProperty('getPlugins'); + }); +}); diff --git a/tests/resolvers/user_query/tasksByUser.spec.js b/tests/resolvers/user_query/tasksByUser.spec.js new file mode 100644 index 0000000000..fcc29ab62c --- /dev/null +++ b/tests/resolvers/user_query/tasksByUser.spec.js @@ -0,0 +1,127 @@ +const shortid = require('shortid'); +const database = require('../../../db'); +const signup = require('../../../lib/resolvers/auth_mutations/signup'); +const createOrganization = require('../../../lib/resolvers/organization_mutations/createOrganization'); +const createEvent = require('../../../lib/resolvers/event_mutations/createEvent'); +const createTask = require('../../../lib/resolvers/project_task_mutations/createTask'); +const tasksByUser = require('../../../lib/resolvers/user_query/tasksByUser'); + +let createOrgResponse; +let userId; + +beforeAll(async () => { + require('dotenv').config(); // pull env variables from .env file + await database.connect(); + + // SignUp the User + let nameForNewUser = shortid.generate().toLowerCase(); + let email = `${nameForNewUser}@test.com`; + let args = { + data: { + firstName: nameForNewUser, + lastName: nameForNewUser, + email: email, + password: 'password', + }, + }; + const signUpResponse = await signup({}, args); + + const name = shortid.generate().toLowerCase(); + const isPublic_boolean = Math.random() < 0.5; + const visibleInSearch_boolean = Math.random() < 0.5; + + args = { + data: { + name: name, + description: name, + isPublic: isPublic_boolean, + visibleInSearch: visibleInSearch_boolean, + apiUrl: name, + }, + }; + + userId = signUpResponse.user._id.toString(); + + const context = { + userId, + }; + + createOrgResponse = await createOrganization({}, args, context); + + const event_isPublic_boolean = Math.random() < 0.5; + const event_isRegisterable_boolean = Math.random() < 0.5; + const event_recurring_boolean = Math.random() < 0.5; + const event_allDay_boolean = Math.random() < 0.5; + + args = { + data: { + organizationId: createOrgResponse._id, + title: 'Talawa Conference Test', + description: 'National conference that happens yearly', + isPublic: event_isPublic_boolean, + isRegisterable: event_isRegisterable_boolean, + recurring: event_recurring_boolean, + recurrance: 'YEARLY', + location: 'Test', + startDate: '2/2/2020', + endDate: '2/2/2022', + allDay: event_allDay_boolean, + endTime: '2:00 PM', + startTime: '1:00 PM', + }, + }; + + let createEventResponse = await createEvent({}, args, context); + + args = { + eventId: createEventResponse._id, + data: { + title: 'title', + description: 'description', + status: 'ACTIVE', + }, + }; + + await createTask({}, args, context); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing tasks by user resolver', () => { + test('task by user', async () => { + const args = { + id: userId, + }; + + const response = await tasksByUser({}, args); + + expect(response).toBeTruthy(); + }); + + const orderByArgs = [ + 'id_ASC', + 'id_DESC', + 'title_ASC', + 'title_DESC', + 'description_ASC', + 'description_DESC', + 'createdAt_ASC', + 'createdAt_DESC', + 'deadline_ASC', + ]; + + orderByArgs.map((arg) => { + test(`Tasks by user with orderBy ${arg}`, async () => { + let args = { + orderBy: arg, + id: userId, + }; + + const response = await tasksByUser({}, args); + + expect(response).toBeTruthy(); + }); + }); +}); diff --git a/tests/updateUserType.spec.js b/tests/updateUserType.spec.js new file mode 100644 index 0000000000..fd650ae17f --- /dev/null +++ b/tests/updateUserType.spec.js @@ -0,0 +1,74 @@ +const mongoose = require('mongoose'); +const shortid = require('shortid'); +const { USER_NOT_AUTHORIZED, USER_NOT_FOUND } = require('../constants'); + +const database = require('../db'); +const User = require('../lib/models/User'); +const updateUserType = require('../lib/resolvers/user_mutations/updateUserType'); +const getUserId = require('./functions/getUserIdFromSignup'); + +let userId; + +beforeAll(async () => { + require('dotenv').config(); + await database.connect(); + let generatedEmail = `${shortid.generate().toLowerCase()}@test.com`; + userId = await getUserId(generatedEmail); +}); + +afterAll(() => { + database.disconnect(); +}); + +describe('Testing update user type resolver', () => { + test('update user type', async () => { + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const args = { + data: { + id: userId, + userType: 'SUPERADMIN', + }, + }; + + const response = await updateUserType({}, args, { + userId, + }); + + expect(response).toBeTruthy(); + }); + + test('Testing, when user is not super admin', async () => { + await User.findByIdAndUpdate({ _id: userId }, { userType: 'ADMIN' }); + + const args = { + data: { + id: userId, + userType: 'SUPERADMIN', + }, + }; + + await expect(async () => { + await updateUserType({}, args, { + userId, + }); + }).rejects.toEqual(Error(USER_NOT_AUTHORIZED)); + }); + + test('Testing, when user is not found', async () => { + await User.findByIdAndUpdate({ _id: userId }, { userType: 'SUPERADMIN' }); + + const args = { + data: { + id: mongoose.Types.ObjectId(), + userType: 'SUPERADMIN', + }, + }; + + await expect(async () => { + await updateUserType({}, args, { + userId, + }); + }).rejects.toEqual(Error(USER_NOT_FOUND)); + }); +});