diff --git a/client/html/index.html b/client/html/index.html index 57eaed62d..7a48e2559 100644 --- a/client/html/index.html +++ b/client/html/index.html @@ -19,15 +19,21 @@ diff --git a/client/package-lock.json b/client/package-lock.json index d3b2f73ed..0dbd8d1b6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -79,9 +79,9 @@ } }, "@advancedalgos/users-client": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/@advancedalgos/users-client/-/users-client-0.0.15.tgz", - "integrity": "sha512-y2IVYU4FDYg0CH2Gcpwf4tmJBaqPch1H4Z3gddlTc2TUA9o/iUEhV4GvejJ1V5Hs5R9QuN3lT5gJ60gClm794w==", + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@advancedalgos/users-client/-/users-client-0.0.18.tgz", + "integrity": "sha512-uxHVl8hOyMeAbKdyP+Jt7BmX4Gw/gE4Wm2lWvgxQxLjf6lZxwMfrQPaGZgfv/qxEkbaK0rkJzCMVT71LRyMhFg==", "dev": true, "requires": { "@material-ui/core": "^3.2.0", @@ -2729,7 +2729,7 @@ "dependencies": { "jsesc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, @@ -4528,7 +4528,7 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, @@ -4922,7 +4922,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5101,7 +5101,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } @@ -5698,7 +5698,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -7839,7 +7839,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -7992,7 +7992,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, @@ -8260,7 +8260,7 @@ }, "html-webpack-harddisk-plugin": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-0.2.0.tgz", "integrity": "sha1-RlQUpTQhm2nB/YEntYX42GiX+y0=", "dev": true, "requires": { @@ -8299,7 +8299,7 @@ }, "htmlparser2": { "version": "3.3.0", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { @@ -10826,7 +10826,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -15842,7 +15842,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, @@ -16086,7 +16086,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, @@ -17978,7 +17978,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -18788,7 +18788,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -20189,7 +20189,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -20362,7 +20362,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { @@ -22106,7 +22106,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } diff --git a/client/package.json b/client/package.json index 0041a2d3d..f2db3866e 100644 --- a/client/package.json +++ b/client/package.json @@ -23,8 +23,8 @@ "devDependencies": { "@advancedalgos/events-client": "0.0.2", "@advancedalgos/key-vault-client": "^0.0.20", - "@advancedalgos/teams-client": "0.0.24", - "@advancedalgos/users-client": "^0.0.15", + "@advancedalgos/teams-client": "0.0.25", + "@advancedalgos/users-client": "^0.0.18", "@advancedalgos/web-components": "0.0.13", "@babel/core": "^7.1.2", "@babel/plugin-proposal-class-properties": "^7.1.0", @@ -90,9 +90,9 @@ "npm": "^6.4.1", "optimize-css-assets-webpack-plugin": "^5.0.1", "prop-types": "^15.6.2", - "react": "^16.5.2", + "react": "^16.6.1", "react-apollo": "^2.2.4", - "react-dom": "^16.5.2", + "react-dom": "^16.6.1", "react-redux": "^5.1.0", "react-router-dom": "^4.3.1", "redux": "^4.0.1", diff --git a/client/src/views/layout/header/imports.js b/client/src/views/layout/header/imports.js index 0a4cc0f0b..269e2ada2 100644 --- a/client/src/views/layout/header/imports.js +++ b/client/src/views/layout/header/imports.js @@ -2,17 +2,22 @@ import { Search, GamepadRounded, AccessibilityNew, - LibraryAdd + LibraryAdd, + Home, + ImportContacts, + BugReport, + People } from '@material-ui/icons' const usersMenus = { title: 'Users', to: '/users', submenus: [ - { title: 'Your profile', to: '/users/user', icon: Search }, - { title: 'Directory', to: '/users/browse', icon: GamepadRounded }, - { title: 'Search', to: '/users/search', icon: AccessibilityNew }, - { title: 'Report', to: 'https://github.com/AdvancedAlgos/UsersModule/issues/new', icon: LibraryAdd, externalLink: true } + { title: 'Home', to: '/users/', icon: Home }, + { title: 'Directory', to: '/users/browse', icon: ImportContacts }, + { title: 'Search', to: '/users/search', icon: Search }, + { title: 'Report', to: 'https://github.com/AdvancedAlgos/UsersModule/issues/new', icon: BugReport, externalLink: true }, + { title: 'Your Profile', to: '/users/user', icon: People } ] } diff --git a/server/.env.example b/server/.env.example index 4041fe324..ddb7185f4 100644 --- a/server/.env.example +++ b/server/.env.example @@ -14,3 +14,10 @@ AUTH0_AUDIENCE=auth0-audience AUTH0_ISSUER=auth0-issuer AACLOUD_ID=aacloud-id + +RECAPTCHA= + +SG_APIKEY= +SG_APIKEY_CONTACTS= +SG_SIGNUP_EMAILID= +SG_CONTACT_EMAILID= diff --git a/server/package-lock.json b/server/package-lock.json index fd5c655c7..61262e73f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -3862,6 +3862,11 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, + "lodash.merge": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", diff --git a/server/package.json b/server/package.json index 57e2c3caa..c7a16fab5 100644 --- a/server/package.json +++ b/server/package.json @@ -36,6 +36,7 @@ "graphql": "^14.0.2", "graphql-tools": "^4.0.0", "jwks-rsa": "^1.3.0", + "lodash.merge": "^4.6.1", "node-fetch": "^2.2.0", "winston": "^3.1.0" } diff --git a/server/src/index.js b/server/src/index.js index 1c7549d00..0201a1056 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -9,6 +9,8 @@ import axios from 'axios' import { createTransformedRemoteSchema } from './createRemoteSchema' import { teams, events } from './links' +import { typeDef as masterSchema } from './schema' +import masterResolvers from './resolvers' import logger from './logger' async function getUserId (authId) { @@ -58,8 +60,9 @@ async function run () { process.env.KEYVAULT_API_URL, process.env.KEYVAULT_API_PRESHARED) - var schemas = [] + var schemas = [masterSchema] var resolvers = {} + resolvers = Object.assign(resolvers, masterResolvers) if (transformedTeamsSchema) { schemas.push(transformedTeamsSchema) @@ -154,7 +157,7 @@ async function run () { schema, context, formatError: error => { - logger.error('An error ocurred inside a module: ' + error) + logger.error(`An error ocurred inside a module: ${JSON.stringify(error)}`) return error }, formatResponse: response => { diff --git a/server/src/resolvers/index.js b/server/src/resolvers/index.js new file mode 100644 index 000000000..c260c10b3 --- /dev/null +++ b/server/src/resolvers/index.js @@ -0,0 +1,10 @@ +import merge from 'lodash.merge' +import { SendgridMutations } from './mutation' + +const resolvers = { + Mutation: { + ...SendgridMutations + } +} + +export default resolvers diff --git a/server/src/resolvers/mutation/index.js b/server/src/resolvers/mutation/index.js new file mode 100644 index 000000000..70ad9aef6 --- /dev/null +++ b/server/src/resolvers/mutation/index.js @@ -0,0 +1,3 @@ +import SendgridMutations from './sendgrid' + +export { SendgridMutations } diff --git a/server/src/resolvers/mutation/sendgrid.js b/server/src/resolvers/mutation/sendgrid.js new file mode 100644 index 000000000..af9e318d5 --- /dev/null +++ b/server/src/resolvers/mutation/sendgrid.js @@ -0,0 +1,251 @@ +import axios from 'axios' +import jwt from 'jsonwebtoken' +import { ApolloError } from 'apollo-server-express' + +import logger from '../../logger' + +const API_KEY = process.env.SG_APIKEY +const API_KEY_CONTACTS = process.env.SG_APIKEY_CONTACTS + +const dev = process.env.NODE_ENV === 'development' ? true : false +let origin = 'https://app.advancedalgos.net' +if (dev){ + origin = 'http://localhost:3100' +} + +export default { + async master_NewsletterSignup(parent, { email }, ctx, info) { + const token = jwt.sign({ email: email }, API_KEY, { expiresIn: '1d' }) + let origin = 'https://advancedalgos.net' + const params = '/email-verification?token=' + if (dev){ + origin = 'http://localhost:4100' + } + + const headers = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin' : origin, + 'Access-Control-Allow-Credentials': 'true' + } + + const data = JSON.stringify({ + "personalizations": [ + { + "to": [ + { + "email": email + } + ], + dynamic_template_data: { + "aaverifylink": origin + params + token, + "subject": 'VERIFY YOUR INTEREST in Advanced Algos' + }, + "subject": 'VERIFY YOUR INTEREST in Advanced Algos' + } + ], + "from": { + "email": "feedback@advancedalgos.net", + "name": "Advanced Algos Team" + }, + "reply_to": { + "email": "feedback@advancedalgos.net", + "name": "Advanced Algos Team" + }, + "template_id": process.env.SG_SIGNUP_EMAILID + }) + + const sendVerify = axios({ + method: 'post', + url: 'https://api.sendgrid.com/v3/mail/send', + data: data, + headers:{ + 'content-type': 'application/json', + 'authorization': 'Bearer ' + API_KEY + } + }) + .then(function (response) { + logger.info('Sendgrid sendVerify response:') + logger.info(response) + if (response.status >= 200 && response.status < 300) { + return 'SUCCESS' + } else { + throw response.data.errors[0].message + } + }) + .catch(function (error) { + logger.error('Sendgrid sendVerify Error:') + logger.error(error) + throw new ApolloError(`Sendgrid sendVerify Error: ${error.response.data.errors[0].message}`, 404) + }) + return sendVerify + }, + async master_NewsletterSignupVerify(parent, { token }, ctx, info){ + let origin = 'https://app.advancedalgos.net' + if (dev){ + origin = 'http://localhost:4100' + } + const headers = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin' : origin, + 'Access-Control-Allow-Credentials': 'true' + } + + let verifiedToken + try { + verifiedToken = jwt.verify(token, API_KEY, {maxAge: '1d'}) + } catch(err) { + logger.error('Sendgrid verifiedToken Error:') + logger.error(err) + if (err.name === 'TokenExpiredError'){ + throw new ApolloError('Error: Token Expired. Please resubmit email address.', 400) + } else { + throw new ApolloError(`Error: ${err.message}`, 400) + } + } + logger.info('Sendgrid verifySignup verifiedToken: ') + logger.info(verifiedToken) + + const email = verifiedToken.email + + const subscribe = axios({ + method: 'post', + url: 'https://api.sendgrid.com/v3/contactdb/recipients', + data: [{"email": email}], + headers:{ + 'content-type': 'application/json', + 'authorization': 'Bearer ' + API_KEY_CONTACTS + } + }) + .then(response => { + logger.info('Sendgrid verifySignup subscribe: ') + logger.info(response) + const recipients = response.data.persisted_recipients + + if (recipients.length > 0 ){ + const addToList = axios({ + method: 'post', + url: 'https://api.sendgrid.com/v3/contactdb/lists/4068018/recipients/' + recipients[0], + headers:{ + 'content-type': 'application/json', + 'authorization': 'Bearer ' + API_KEY_CONTACTS + } + }) + .then(response => { + logger.info('Sendgrid verifySignup addToList: ') + logger.info(response) + if (response.status >= 200 && response.status < 300) { + return 'SUCCESS' + } else { + throw response.data.errors[0].message + } + }) + .catch(error => { + logger.error('Sendgrid verifySignUp subscribe Error:') + logger.error(error) + throw `Email SignUp Error: ${error}` + }) + return addToList + } else{ + return response.data.errors[0].message + } + }) + .then(response => { + return response + }) + .catch(error => { + logger.error('Sendgrid verifySubscribe Error:') + logger.error(error) + throw new ApolloError(`Sendgrid verifySubscribe Error: ${error.response.data.errors[0].message}`, 404) + }) + return subscribe + }, + async master_Contact(parent, { email, subject, message, recaptcha }, ctx, info) { + let origin = 'https://advancedalgos.net' + if (dev){ + origin = 'http://localhost:4000' + } + const headers = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin' : origin, + 'Access-Control-Allow-Credentials': 'true' + } + + const toEmail = 'feedback@advancedalgos.net' + + const data = JSON.stringify({ + "personalizations": [ + { + "to": [ + { + "email": email + } + ], + dynamic_template_data: { + "aacontactname": name, + "aacontactemail": email, + "aacontactbody": message + }, + "subject": `AA Corporate Site Contact - Message from ${name}` + } + ], + "from": { + "email": email, + "name": name + }, + "reply_to": { + "email": email, + "name": name + }, + "template_id": process.env.SG_CONTACT_EMAILID + }) + + const checkCaptcha = axios({ + method: 'post', + url: 'https://www.google.com/recaptcha/api/siteverify', + params: { + secret: process.env.RECAPTCHA, + response: recaptcha + } + }) + .then(function (response) { + logger.info(response.data) + if (response.status >= 200 && response.status < 300 && response.data.success) { + context.log('presend SendGrid') + return axios({ + method: 'post', + url: 'https://api.sendgrid.com/v3/mail/send', + data: data, + headers:{ + 'content-type': 'application/json', + 'authorization': 'Bearer ' + API_KEY + } + }) + .then(function (response) { + logger.info('Sendgrid Contact response: ') + logger.info(response) + if (response.status >= 200 && response.status < 300) { + return `Contact email sent` + } else { + throw response.data.errors[0].message + } + }) + .catch(function (error) { + logger.error(`sendgrid Error: ${JSON.stringify(error)}`) + throw `Contact email send error: ${error.response.data.errors[0].message}` + }) + }else{ + throw response.data + } + }) + .then(function(response){ + return response + }) + .catch(function (error) { + logger.error('Sendgrid Contact RECAPTCHA Error:') + logger.error(error) + throw new ApolloError(`Sendgrid Contact RECAPTCHA Error: ${error.response.data.errors[0].message}`, 404) + }) + + return checkCaptcha + } +} diff --git a/server/src/schema/index.js b/server/src/schema/index.js new file mode 100644 index 000000000..4b416cceb --- /dev/null +++ b/server/src/schema/index.js @@ -0,0 +1,11 @@ +export const typeDef = ` + type Mutation { + master_NewsletterSignup(email: String!): String + master_NewsletterSignupVerify(token: String!): String + master_Contact(email: String!, subject: String!, Message: String!, recaptcha: String!): String + } + + schema { + mutation: Mutation + } +` diff --git a/server/src/utils.js b/server/src/utils.js index d0d9a81bb..a9c57d8ce 100644 --- a/server/src/utils.js +++ b/server/src/utils.js @@ -1,3 +1,18 @@ +import fs from 'fs' +import path from 'path' + +export const mergeDirectoryModules = dirpath => + fs + .readdirSync(dirpath, 'utf8') + .filter(filename => !/^[_|index.]/.test(filename)) + .reduce( + (acc, filename) => ({ + ...acc, + ...require(path.resolve(dirpath, filename)).default, + }), + {} + ) + export const capitalize = (string) => { return (string).charAt(0).toUpperCase() + (string).slice(1) }