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)
}