From 64a54b4fe8e536f05bc368269580259ba8a4db9e Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Mon, 2 Sep 2019 16:46:25 +0200 Subject: [PATCH 01/72] Add https --- frontend/src/services/socket/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index 889188f4..bcf481da 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -6,7 +6,7 @@ let socket = null; * Creates a socket instance */ export const connectSocket = () => { - socket = socketIOClient("http://localhost:3000", { + socket = socketIOClient("https://localhost:3000", { transports: ["websocket"], rejectUnauthorized: false }); From b034ec7ad089bb147e8224a43a900d3b8751957b Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 3 Sep 2019 13:41:20 +0200 Subject: [PATCH 02/72] Add current host as link --- frontend/src/services/socket/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index bcf481da..cafa152b 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -6,7 +6,7 @@ let socket = null; * Creates a socket instance */ export const connectSocket = () => { - socket = socketIOClient("https://localhost:3000", { + socket = socketIOClient("https://" + window.location.host, { transports: ["websocket"], rejectUnauthorized: false }); From c70329eab3618bb394ba12b95d63943b3e0aac61 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 3 Sep 2019 13:50:56 +0200 Subject: [PATCH 03/72] Add console output for socket error --- frontend/src/services/socket/base.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index cafa152b..169eae0a 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -11,6 +11,7 @@ export const connectSocket = () => { rejectUnauthorized: false }); socket.on("connect_error", err => { + console.log(err); socket.disconnect(); }); return socket; From cfbd4f181379db0351e056bb052896eca9f01785 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 3 Sep 2019 14:46:14 +0200 Subject: [PATCH 04/72] Remove ssl from socket url for testing purposes --- frontend/src/services/socket/base.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index 169eae0a..b10b809d 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -6,12 +6,12 @@ let socket = null; * Creates a socket instance */ export const connectSocket = () => { - socket = socketIOClient("https://" + window.location.host, { + socket = socketIOClient("http://" + window.location.host, { transports: ["websocket"], rejectUnauthorized: false }); socket.on("connect_error", err => { - console.log(err); + console.log("Socket connect error", err); socket.disconnect(); }); return socket; From 35d83191fe21863aac2907924185c610532c58a8 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 3 Sep 2019 14:51:30 +0200 Subject: [PATCH 05/72] Add more logs, put back ssl --- frontend/src/services/socket/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index b10b809d..80b757c3 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -6,7 +6,7 @@ let socket = null; * Creates a socket instance */ export const connectSocket = () => { - socket = socketIOClient("http://" + window.location.host, { + socket = socketIOClient("https://" + window.location.host, { transports: ["websocket"], rejectUnauthorized: false }); From a8393c1e9235c15e2c01fa7b608dc72add9d1b22 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 3 Sep 2019 14:51:37 +0200 Subject: [PATCH 06/72] Add more logs, put back ssl --- backend/src/models/user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/models/user.js b/backend/src/models/user.js index ddf87682..7ac62753 100644 --- a/backend/src/models/user.js +++ b/backend/src/models/user.js @@ -1,6 +1,6 @@ -import Sequelize from 'sequelize'; -import jwt from 'jsonwebtoken'; import { compare, hash } from 'bcrypt'; +import jwt from 'jsonwebtoken'; +import Sequelize from 'sequelize'; class User extends Sequelize.Model { static init(sequelize, DataTypes) { @@ -183,7 +183,9 @@ class User extends Sequelize.Model { name: user.name, surname: user.surname, }; + console.log(objToSign); const token = jwt.sign(objToSign, process.env.JWT_SECRET).toString(); + console.log('Token', token); user.tokens.push(token); await user.update({ tokens: user.tokens }); return token; From c581c1b420efe533721c74dd6d1a335ee9edd2de Mon Sep 17 00:00:00 2001 From: bjusufbe Date: Wed, 4 Sep 2019 13:21:59 +0200 Subject: [PATCH 07/72] Enhancing Jenkinsfile to propagate jwt_secret/cookie_s credentials --- ops/Jenkinsfile | 41 +++++++++++++++++------------- ops/helm/templates/deployment.yaml | 6 +++++ ops/helm/values.deploy.yaml | 2 ++ ops/helm/values.yaml | 2 ++ 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/ops/Jenkinsfile b/ops/Jenkinsfile index ab31ca7b..49d96f4c 100644 --- a/ops/Jenkinsfile +++ b/ops/Jenkinsfile @@ -48,26 +48,31 @@ spec: } } } + stage('Deploy helm chart') { steps { - container('helm') { - sh ''' - #!/bin/bash - - if [[ "$ENVIRONMENT" == "deploy" ]]; then - values_arg="-f ops/helm/values.deploy.yaml" - fi; - helm upgrade aa-${ENVIRONMENT} \ - --recreate-pods \ - --namespace aa \ - --install $values_arg \ - --set image.tag=${IMAGE_TAG} \ - --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ - --set ingress.hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ - ops/helm/ - ''' + withCredentials([string(credentialsId: 'JWT_SECRET', variable: 'JWT_SECRET'), string(credentialsId: 'COOKIE_S', variable: 'COOKIE_S')]) { + container('helm') { + sh ''' + #!/bin/bash + + if [[ "$ENVIRONMENT" == "deploy" ]]; then + values_arg="-f ops/helm/values.deploy.yaml" + fi; + helm upgrade aa-${ENVIRONMENT} \ + --recreate-pods \ + --namespace aa \ + --install $values_arg \ + --set image.tag=${IMAGE_TAG} \ + --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ + --set ingress.hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ + --set config.jwtSecret=${JWT_SECRET} \ + --set config.cookieS=${COOKIE_S} \ + ops/helm/ + ''' + } } } } diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index 94b78a08..177705c7 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -65,6 +65,12 @@ spec: value: "465" - name: PORT value: "3000" + - name: JWT_SECRET + value: {{ .Values.config.jwtSecret }} + - name: COOKIE_S + value: {{ .Values.config.cookieS }} + - name: SALT_ROUNDS + value: 10 livenessProbe: httpGet: path: / diff --git a/ops/helm/values.deploy.yaml b/ops/helm/values.deploy.yaml index 6e6e6847..93b3a386 100644 --- a/ops/helm/values.deploy.yaml +++ b/ops/helm/values.deploy.yaml @@ -17,4 +17,6 @@ config: dbHost: atlanters-anonymous-pg-deploy-postgresql dbSecret: atlanters-anonymous-pg-deploy-postgresql smtpSecret: atlanters-anonymous-smtp-deploy + jwtSecret: jwt-secret-deploy + cookieS: cookie-s-deploy diff --git a/ops/helm/values.yaml b/ops/helm/values.yaml index 4b9ccbc0..38005b62 100644 --- a/ops/helm/values.yaml +++ b/ops/helm/values.yaml @@ -31,6 +31,8 @@ config: dbHost: atlanters-anonymous-pg-postgresql dbSecret: atlanters-anonymous-pg-postgresql smtpSecret: atlanters-anonymous-smtp + jwtSecret: jwt-secret + cookieS: cookie-s resources: {} From 13c0efdefe28831291349b44dbfa803b944def5d Mon Sep 17 00:00:00 2001 From: bjusufbe Date: Wed, 4 Sep 2019 13:27:29 +0200 Subject: [PATCH 08/72] small fix for value in deployment.yaml --- ops/helm/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index 177705c7..f7c7589e 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -70,7 +70,7 @@ spec: - name: COOKIE_S value: {{ .Values.config.cookieS }} - name: SALT_ROUNDS - value: 10 + value: "10" livenessProbe: httpGet: path: / From 3d279a6c585bb3c260752ed7791e4ff535612cbb Mon Sep 17 00:00:00 2001 From: fvedad Date: Tue, 3 Sep 2019 21:50:36 +0200 Subject: [PATCH 09/72] Fix chat errors and replace plain text with html in email --- backend/src/models/feedback.js | 2 +- .../routes/feedback/feedback.messages.post.js | 4 ++-- backend/src/routes/feedback/feedback.post.js | 4 ++-- backend/src/routes/utils.js | 14 +++++++++----- .../src/components/common/FeedbackTicket.jsx | 17 ++++++----------- frontend/src/constants/strings.js | 2 +- package-lock.json | 3 +++ 7 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 package-lock.json diff --git a/backend/src/models/feedback.js b/backend/src/models/feedback.js index 917c933d..be6bd88a 100644 --- a/backend/src/models/feedback.js +++ b/backend/src/models/feedback.js @@ -70,7 +70,7 @@ class Feedback extends Sequelize.Model { from: process.env.EMAIL_FEEDBACK, to: process.env.EMAIL_FEEDBACK, subject: 'Feedback Received', - text: text, + html: text, }); } diff --git a/backend/src/routes/feedback/feedback.messages.post.js b/backend/src/routes/feedback/feedback.messages.post.js index f15d9019..06a87244 100644 --- a/backend/src/routes/feedback/feedback.messages.post.js +++ b/backend/src/routes/feedback/feedback.messages.post.js @@ -1,4 +1,4 @@ -import { GET_DETAILED_MESSAGE } from '../utils'; +import { GET_DETAILED_MESSAGE_HTML } from '../utils'; export default ({ models }) => { const { Feedback, Message, User } = models; @@ -15,7 +15,7 @@ export default ({ models }) => { user = await User.findById(userId); if (!user) throw new Error(`User with id ${userId} does not exist`); } else { - Feedback.sendMail(GET_DETAILED_MESSAGE(feedback, messageReq)); + Feedback.sendMail(GET_DETAILED_MESSAGE_HTML(feedback, messageReq)); } let message = await Message.create(messageReq); if (user) await user.addMessage(message); diff --git a/backend/src/routes/feedback/feedback.post.js b/backend/src/routes/feedback/feedback.post.js index a582b17f..3dcc6607 100644 --- a/backend/src/routes/feedback/feedback.post.js +++ b/backend/src/routes/feedback/feedback.post.js @@ -1,4 +1,4 @@ -import { GET_DETAILED_MESSAGE } from '../utils'; +import { GET_DETAILED_MESSAGE_HTML } from '../utils'; export default ({ models }) => { const { Feedback, Message } = models; @@ -6,7 +6,7 @@ export default ({ models }) => { const messageReq = req.body; try { let feedback = await Feedback.create(); - await Feedback.sendMail(GET_DETAILED_MESSAGE(feedback, messageReq)); + await Feedback.sendMail(GET_DETAILED_MESSAGE_HTML(feedback, messageReq)); const message = await Message.create(messageReq); feedback = await feedback.addMessage(message); res.send(feedback); diff --git a/backend/src/routes/utils.js b/backend/src/routes/utils.js index 2de7f828..e8968015 100644 --- a/backend/src/routes/utils.js +++ b/backend/src/routes/utils.js @@ -12,12 +12,16 @@ export const FORMAT_DATE = date => { return dateformat(date, 'dd/mm/yyyy HH:MM'); }; -export const GET_DETAILED_MESSAGE = (feedback, { text }) => { - const ticketId = `Ticket id: ${feedback.id}\n`; - const ticketCreatedAt = `Ticket created at: ${FORMAT_DATE( +export const GET_DETAILED_MESSAGE_HTML = (feedback, { text }) => { + const ticketId = `Ticket id: ${ + feedback.id + }
`; + const ticketCreatedAt = `Ticket created at: ${FORMAT_DATE( feedback.createdAt - )}\n`; - const messageSentAt = `Message sent at: ${FORMAT_DATE(new Date())}\n\n`; + )}
`; + const messageSentAt = `Message sent at: ${FORMAT_DATE( + new Date() + )}

`; const details = ticketId + ticketCreatedAt + messageSentAt; return { text: details + text }; }; diff --git a/frontend/src/components/common/FeedbackTicket.jsx b/frontend/src/components/common/FeedbackTicket.jsx index ae3bbff6..936fc106 100644 --- a/frontend/src/components/common/FeedbackTicket.jsx +++ b/frontend/src/components/common/FeedbackTicket.jsx @@ -47,6 +47,10 @@ export default class FeedbackTicket extends Component { }, isMessageSubmitting: false, seen: false, + /** + * Checks if this client is the author of received chat message + */ + isAuthorCurrentClient: false, error: "" }; @@ -65,16 +69,6 @@ export default class FeedbackTicket extends Component { this.messagesEnd.scrollIntoView(); }; - /** - * Checks if the client is the author of received chat message - * - * @param {String} messageAuthor received message author name - * @param {String} currentClient name of current client - */ - isAuthorCurrentClient = (messageAuthor, currentClient) => { - return messageAuthor === currentClient; - }; - /** * Called when server emits a message through socket * @@ -82,7 +76,7 @@ export default class FeedbackTicket extends Component { */ onChatMessageReceived = data => { const { messages, user } = this.state; - if (!this.isAuthorCurrentClient(data.User.name, user.name)) { + if (!this.state.isAuthorCurrentClient) { messages.push(data); const latestAuthorName = this.resolveAuthorName(data); this.setState({ @@ -252,6 +246,7 @@ export default class FeedbackTicket extends Component { this.setState({ messages, isMessageSubmitting: false, + isAuthorCurrentClient: true, seen: false, latestAuthorName, inputMessage: "" diff --git a/frontend/src/constants/strings.js b/frontend/src/constants/strings.js index 57de3399..f87de19f 100644 --- a/frontend/src/constants/strings.js +++ b/frontend/src/constants/strings.js @@ -2,4 +2,4 @@ export const CHAT_EVENT = "chat"; export const SEEN_EVENT = "seen"; export const USER_LAST_SEEN = "userLastSeenAt"; -export const ANONYMOUS_LAST_SEEN = "anonymousLastSeenAt"; +export const ANONYMOUS_LAST_SEEN = "anonymLastSeenAt"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..48e341a0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} From c8e32468d432f1f97a6d841b26726eb9519924cc Mon Sep 17 00:00:00 2001 From: bjusufbe Date: Wed, 4 Sep 2019 14:04:49 +0200 Subject: [PATCH 10/72] Removed passing app creds from Jenkins credentials, using k8s secret for that --- ops/Jenkinsfile | 42 ++++++++++++++---------------- ops/helm/templates/deployment.yaml | 10 +++++-- ops/helm/values.deploy.yaml | 3 +-- ops/helm/values.yaml | 3 +-- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/ops/Jenkinsfile b/ops/Jenkinsfile index 49d96f4c..9abbc35d 100644 --- a/ops/Jenkinsfile +++ b/ops/Jenkinsfile @@ -51,28 +51,26 @@ spec: stage('Deploy helm chart') { steps { - withCredentials([string(credentialsId: 'JWT_SECRET', variable: 'JWT_SECRET'), string(credentialsId: 'COOKIE_S', variable: 'COOKIE_S')]) { - container('helm') { - sh ''' - #!/bin/bash - - if [[ "$ENVIRONMENT" == "deploy" ]]; then - values_arg="-f ops/helm/values.deploy.yaml" - fi; - helm upgrade aa-${ENVIRONMENT} \ - --recreate-pods \ - --namespace aa \ - --install $values_arg \ - --set image.tag=${IMAGE_TAG} \ - --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ - --set ingress.hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ - --set config.jwtSecret=${JWT_SECRET} \ - --set config.cookieS=${COOKIE_S} \ - ops/helm/ - ''' - } + container('helm') { + sh ''' + #!/bin/bash + + if [[ "$ENVIRONMENT" == "deploy" ]]; then + values_arg="-f ops/helm/values.deploy.yaml" + fi; + helm upgrade aa-${ENVIRONMENT} \ + --recreate-pods \ + --namespace aa \ + --install $values_arg \ + --set image.tag=${IMAGE_TAG} \ + --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ + --set ingress.hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ + --set config.jwtSecret=${JWT_SECRET} \ + --set config.cookieS=${COOKIE_S} \ + ops/helm/ + ''' } } } diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index f7c7589e..dee7e9c7 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -66,9 +66,15 @@ spec: - name: PORT value: "3000" - name: JWT_SECRET - value: {{ .Values.config.jwtSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: jwt_secret - name: COOKIE_S - value: {{ .Values.config.cookieS }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: cookie_s - name: SALT_ROUNDS value: "10" livenessProbe: diff --git a/ops/helm/values.deploy.yaml b/ops/helm/values.deploy.yaml index 93b3a386..6ab465fd 100644 --- a/ops/helm/values.deploy.yaml +++ b/ops/helm/values.deploy.yaml @@ -17,6 +17,5 @@ config: dbHost: atlanters-anonymous-pg-deploy-postgresql dbSecret: atlanters-anonymous-pg-deploy-postgresql smtpSecret: atlanters-anonymous-smtp-deploy - jwtSecret: jwt-secret-deploy - cookieS: cookie-s-deploy + appSecret: atlanters-anonymous-app-deploy diff --git a/ops/helm/values.yaml b/ops/helm/values.yaml index 38005b62..bad4542e 100644 --- a/ops/helm/values.yaml +++ b/ops/helm/values.yaml @@ -31,8 +31,7 @@ config: dbHost: atlanters-anonymous-pg-postgresql dbSecret: atlanters-anonymous-pg-postgresql smtpSecret: atlanters-anonymous-smtp - jwtSecret: jwt-secret - cookieS: cookie-s + appSecret: atlanters-anonymous-app resources: {} From 11091d981fd1043222a0a6dbd301f5f6e3e29fa2 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Fri, 6 Sep 2019 14:15:47 +0200 Subject: [PATCH 11/72] Fix resolve socket host for different envs --- backend/src/models/message.js | 2 +- .../routes/feedback/feedback.messages.post.js | 7 +++++-- backend/src/routes/feedback/feedback.post.js | 7 +++++-- backend/src/routes/utils.js | 10 ++++++---- backend/src/sockets/events/chat.js | 6 +++--- .../src/components/common/FeedbackItem.jsx | 12 +---------- .../src/components/common/FeedbackList.jsx | 1 - .../src/components/common/FeedbackTicket.jsx | 20 ++++++++----------- frontend/src/services/socket/base.js | 4 ++-- frontend/src/services/socket/chat.js | 4 ++-- frontend/src/styles/common/feedbackItem.css | 4 ++-- frontend/src/utils/strings.js | 5 +++++ 12 files changed, 40 insertions(+), 42 deletions(-) diff --git a/backend/src/models/message.js b/backend/src/models/message.js index 7fcee363..aa0bee8e 100644 --- a/backend/src/models/message.js +++ b/backend/src/models/message.js @@ -48,7 +48,7 @@ class Message extends Sequelize.Model { include: [ { model: User, - attributes: ['name', 'surname'], + attributes: ['id', 'name', 'surname'], }, ], }); diff --git a/backend/src/routes/feedback/feedback.messages.post.js b/backend/src/routes/feedback/feedback.messages.post.js index 06a87244..dc95c678 100644 --- a/backend/src/routes/feedback/feedback.messages.post.js +++ b/backend/src/routes/feedback/feedback.messages.post.js @@ -1,4 +1,4 @@ -import { GET_DETAILED_MESSAGE_HTML } from '../utils'; +import { GET_DETAILED_MESSAGE_HTML, GET_FEEDBACK_URL } from '../utils'; export default ({ models }) => { const { Feedback, Message, User } = models; @@ -15,7 +15,10 @@ export default ({ models }) => { user = await User.findById(userId); if (!user) throw new Error(`User with id ${userId} does not exist`); } else { - Feedback.sendMail(GET_DETAILED_MESSAGE_HTML(feedback, messageReq)); + const feedbackUrl = GET_FEEDBACK_URL(req, feedbackId); + Feedback.sendMail( + GET_DETAILED_MESSAGE_HTML(feedback, feedbackUrl, messageReq) + ); } let message = await Message.create(messageReq); if (user) await user.addMessage(message); diff --git a/backend/src/routes/feedback/feedback.post.js b/backend/src/routes/feedback/feedback.post.js index 3dcc6607..d7c04fcc 100644 --- a/backend/src/routes/feedback/feedback.post.js +++ b/backend/src/routes/feedback/feedback.post.js @@ -1,4 +1,4 @@ -import { GET_DETAILED_MESSAGE_HTML } from '../utils'; +import { GET_DETAILED_MESSAGE_HTML, GET_FEEDBACK_URL } from '../utils'; export default ({ models }) => { const { Feedback, Message } = models; @@ -6,7 +6,10 @@ export default ({ models }) => { const messageReq = req.body; try { let feedback = await Feedback.create(); - await Feedback.sendMail(GET_DETAILED_MESSAGE_HTML(feedback, messageReq)); + const feedbackUrl = GET_FEEDBACK_URL(req, feedback.id); + await Feedback.sendMail( + GET_DETAILED_MESSAGE_HTML(feedback, feedbackUrl, messageReq) + ); const message = await Message.create(messageReq); feedback = await feedback.addMessage(message); res.send(feedback); diff --git a/backend/src/routes/utils.js b/backend/src/routes/utils.js index e8968015..9f2514b2 100644 --- a/backend/src/routes/utils.js +++ b/backend/src/routes/utils.js @@ -12,10 +12,12 @@ export const FORMAT_DATE = date => { return dateformat(date, 'dd/mm/yyyy HH:MM'); }; -export const GET_DETAILED_MESSAGE_HTML = (feedback, { text }) => { - const ticketId = `Ticket id: ${ - feedback.id - }
`; +export const GET_FEEDBACK_URL = (req, feedbackId) => { + return req.protocol + '://' + req.get('host') + '/feedback/' + feedbackId; +}; + +export const GET_DETAILED_MESSAGE_HTML = (feedback, feedbackUrl, { text }) => { + const ticketId = `Ticket id: ${feedback.id}
`; const ticketCreatedAt = `Ticket created at: ${FORMAT_DATE( feedback.createdAt )}
`; diff --git a/backend/src/sockets/events/chat.js b/backend/src/sockets/events/chat.js index ef435fb0..c4769c44 100644 --- a/backend/src/sockets/events/chat.js +++ b/backend/src/sockets/events/chat.js @@ -2,11 +2,11 @@ import { CHAT_EVENT } from '../../constants/socket'; export default (io, models, socket) => { socket.on(CHAT_EVENT, async data => { - const { feedbackId, message } = data; + const { feedbackId, messages } = data; try { - io.sockets.emit(feedbackId, message); + io.sockets.emit(feedbackId, messages); } catch (err) { - io.sockets.emit(message.User.id, err.toString()); + io.sockets.emit(messages[messages.length - 1].User.id, err.toString()); } }); }; diff --git a/frontend/src/components/common/FeedbackItem.jsx b/frontend/src/components/common/FeedbackItem.jsx index c1ec038e..dc71cd24 100644 --- a/frontend/src/components/common/FeedbackItem.jsx +++ b/frontend/src/components/common/FeedbackItem.jsx @@ -10,11 +10,6 @@ export default class FeedbackItem extends Component { */ createdAt: PropTypes.string.isRequired, - /** - * Latest message from ticket - */ - message: PropTypes.string.isRequired, - /** * Is ticket closed */ @@ -36,12 +31,8 @@ export default class FeedbackItem extends Component { this.props.onCloseFeedback(this.props.id); }; - outputMessage = message => { - return message.length > 40 ? message.slice(0, 40) + "..." : message; - }; - render() { - const { createdAt, message, isClosed, userSeenAt } = this.props; + const { createdAt, isClosed, userSeenAt } = this.props; return (
@@ -50,7 +41,6 @@ export default class FeedbackItem extends Component {
{dateformat(userSeenAt, "dd/mm/yyyy HH:MM")}
-
{this.outputMessage(message)}
{!isClosed ? (
diff --git a/frontend/src/components/common/FeedbackList.jsx b/frontend/src/components/common/FeedbackList.jsx index 5117a42f..39cefaee 100644 --- a/frontend/src/components/common/FeedbackList.jsx +++ b/frontend/src/components/common/FeedbackList.jsx @@ -89,7 +89,6 @@ export default class FeedbackList extends Component { >
Feedback Created
Latest user visit
-
Latest message
Manage ticket
diff --git a/frontend/src/components/common/FeedbackTicket.jsx b/frontend/src/components/common/FeedbackTicket.jsx index 936fc106..46ad3dec 100644 --- a/frontend/src/components/common/FeedbackTicket.jsx +++ b/frontend/src/components/common/FeedbackTicket.jsx @@ -75,17 +75,13 @@ export default class FeedbackTicket extends Component { * @param {Object} data corresponds to message model from the server */ onChatMessageReceived = data => { - const { messages, user } = this.state; - if (!this.state.isAuthorCurrentClient) { - messages.push(data); - const latestAuthorName = this.resolveAuthorName(data); - this.setState({ - messages, - isMessageSubmitting: false, - seen: false, - latestAuthorName - }); - } + const latestAuthorName = this.resolveAuthorName(data[data.length - 1]); + this.setState({ + messages: data, + isMessageSubmitting: false, + seen: false, + latestAuthorName + }); }; /** @@ -251,7 +247,7 @@ export default class FeedbackTicket extends Component { latestAuthorName, inputMessage: "" }); - emitMessage(this.props.feedback.id, res); + emitMessage(this.props.feedback.id, messages); }; onPostFeedbackMessageError = err => { diff --git a/frontend/src/services/socket/base.js b/frontend/src/services/socket/base.js index 80b757c3..9b391fdf 100644 --- a/frontend/src/services/socket/base.js +++ b/frontend/src/services/socket/base.js @@ -1,12 +1,12 @@ import socketIOClient from "socket.io-client"; - +import { getHostnameWithProtocol } from "../../utils/strings"; let socket = null; /** * Creates a socket instance */ export const connectSocket = () => { - socket = socketIOClient("https://" + window.location.host, { + socket = socketIOClient(getHostnameWithProtocol(window), { transports: ["websocket"], rejectUnauthorized: false }); diff --git a/frontend/src/services/socket/chat.js b/frontend/src/services/socket/chat.js index 5b1b5f36..8d375008 100644 --- a/frontend/src/services/socket/chat.js +++ b/frontend/src/services/socket/chat.js @@ -32,8 +32,8 @@ export const onSeen = (feedbackId, callback) => { * @param {Number} userId id of user sending the message (null if anonymous) * @param {String} feedbackId uuid of feedback object */ -export const emitMessage = (feedbackId, message) => { - emit(CHAT_EVENT, { feedbackId, message }); +export const emitMessage = (feedbackId, messages) => { + emit(CHAT_EVENT, { feedbackId, messages }); }; export const emitSeen = (user, feedbackId, date) => { diff --git a/frontend/src/styles/common/feedbackItem.css b/frontend/src/styles/common/feedbackItem.css index d1c97da7..25808f16 100644 --- a/frontend/src/styles/common/feedbackItem.css +++ b/frontend/src/styles/common/feedbackItem.css @@ -1,7 +1,7 @@ .feedback-item-container { grid-column: 2; display: grid; - grid-template-columns: auto auto 0.8fr auto; + grid-template-columns: auto auto 0.8fr; background-color: white; border-radius: 6px; padding: 15px; @@ -47,7 +47,7 @@ } .feedback-item-container .closed { - width: 100px; + width: 100%; } .feedback-item-container .closed .button { diff --git a/frontend/src/utils/strings.js b/frontend/src/utils/strings.js index e278747a..8857830a 100644 --- a/frontend/src/utils/strings.js +++ b/frontend/src/utils/strings.js @@ -7,3 +7,8 @@ export const validateInputMessage = message => { return "Messages greater than 1000 characters are not allowed"; return null; }; + +export const getHostnameWithProtocol = ({ location }) => { + const { protocol, host } = location; + return protocol + "//" + host; +}; From e1faf897d7a93eccf27fd6f9657033206b299e8d Mon Sep 17 00:00:00 2001 From: bjusufbe Date: Wed, 4 Sep 2019 14:04:49 +0200 Subject: [PATCH 12/72] Removed passing app creds from Jenkins credentials, using k8s secret for that --- ops/Jenkinsfile | 42 ++++++++++++++---------------- ops/helm/templates/deployment.yaml | 10 +++++-- ops/helm/values.deploy.yaml | 3 +-- ops/helm/values.yaml | 3 +-- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/ops/Jenkinsfile b/ops/Jenkinsfile index 49d96f4c..7bafb114 100644 --- a/ops/Jenkinsfile +++ b/ops/Jenkinsfile @@ -51,28 +51,26 @@ spec: stage('Deploy helm chart') { steps { - withCredentials([string(credentialsId: 'JWT_SECRET', variable: 'JWT_SECRET'), string(credentialsId: 'COOKIE_S', variable: 'COOKIE_S')]) { - container('helm') { - sh ''' - #!/bin/bash - - if [[ "$ENVIRONMENT" == "deploy" ]]; then - values_arg="-f ops/helm/values.deploy.yaml" - fi; - helm upgrade aa-${ENVIRONMENT} \ - --recreate-pods \ - --namespace aa \ - --install $values_arg \ - --set image.tag=${IMAGE_TAG} \ - --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ - --set ingress.hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ - --set config.jwtSecret=${JWT_SECRET} \ - --set config.cookieS=${COOKIE_S} \ - ops/helm/ - ''' - } + container('helm') { + sh ''' + #!/bin/bash + + if [[ "$ENVIRONMENT" == "deploy" ]]; then + values_arg="-f ops/helm/values.deploy.yaml" + fi; + helm upgrade aa-${ENVIRONMENT} \ + --recreate-pods \ + --namespace aa \ + --install $values_arg \ + --set image.tag=${IMAGE_TAG} \ + --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ + --set ingress.hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ + --set config.jwtSecret=${JWT_SECRET} \ + --set config.cookieS=${COOKIE_S} \ + ops/helm/ + ''' } } } diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index f7c7589e..dee7e9c7 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -66,9 +66,15 @@ spec: - name: PORT value: "3000" - name: JWT_SECRET - value: {{ .Values.config.jwtSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: jwt_secret - name: COOKIE_S - value: {{ .Values.config.cookieS }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: cookie_s - name: SALT_ROUNDS value: "10" livenessProbe: diff --git a/ops/helm/values.deploy.yaml b/ops/helm/values.deploy.yaml index 93b3a386..6ab465fd 100644 --- a/ops/helm/values.deploy.yaml +++ b/ops/helm/values.deploy.yaml @@ -17,6 +17,5 @@ config: dbHost: atlanters-anonymous-pg-deploy-postgresql dbSecret: atlanters-anonymous-pg-deploy-postgresql smtpSecret: atlanters-anonymous-smtp-deploy - jwtSecret: jwt-secret-deploy - cookieS: cookie-s-deploy + appSecret: atlanters-anonymous-app-deploy diff --git a/ops/helm/values.yaml b/ops/helm/values.yaml index 38005b62..bad4542e 100644 --- a/ops/helm/values.yaml +++ b/ops/helm/values.yaml @@ -31,8 +31,7 @@ config: dbHost: atlanters-anonymous-pg-postgresql dbSecret: atlanters-anonymous-pg-postgresql smtpSecret: atlanters-anonymous-smtp - jwtSecret: jwt-secret - cookieS: cookie-s + appSecret: atlanters-anonymous-app resources: {} From 608ead02feab4148f7ce280af401e8a9855c115e Mon Sep 17 00:00:00 2001 From: fvedad Date: Sun, 8 Sep 2019 12:15:16 +0200 Subject: [PATCH 13/72] Add close ticket option below chat input --- backend/src/models/user.js | 2 - .../src/routes/feedback/feedback.seen.put.js | 1 - .../src/components/common/FeedbackList.jsx | 15 ++++++- .../src/components/common/FeedbackTicket.jsx | 45 +++++++++++++++++-- .../src/components/common/TicketMessage.jsx | 3 ++ frontend/src/pages/Feedbacks.jsx | 29 +++++++----- frontend/src/styles/common/feedbackTicket.css | 16 +++++++ 7 files changed, 93 insertions(+), 18 deletions(-) diff --git a/backend/src/models/user.js b/backend/src/models/user.js index 7ac62753..2f4724c0 100644 --- a/backend/src/models/user.js +++ b/backend/src/models/user.js @@ -183,9 +183,7 @@ class User extends Sequelize.Model { name: user.name, surname: user.surname, }; - console.log(objToSign); const token = jwt.sign(objToSign, process.env.JWT_SECRET).toString(); - console.log('Token', token); user.tokens.push(token); await user.update({ tokens: user.tokens }); return token; diff --git a/backend/src/routes/feedback/feedback.seen.put.js b/backend/src/routes/feedback/feedback.seen.put.js index 349efd28..37d385ae 100644 --- a/backend/src/routes/feedback/feedback.seen.put.js +++ b/backend/src/routes/feedback/feedback.seen.put.js @@ -8,7 +8,6 @@ export default ({ models }) => { if (!feedback) throw new Error(`Feedback with id ${feedbackId} does not exist`); const updateResult = await feedback.update(feedbackReq); - console.log(feedbackReq); res.send(updateResult); } catch (error) { next(new Error(error)); diff --git a/frontend/src/components/common/FeedbackList.jsx b/frontend/src/components/common/FeedbackList.jsx index 39cefaee..2313453d 100644 --- a/frontend/src/components/common/FeedbackList.jsx +++ b/frontend/src/components/common/FeedbackList.jsx @@ -19,7 +19,17 @@ export default class FeedbackList extends Component { /** * Current page number */ - currentPage: PropTypes.number.isRequired + currentPage: PropTypes.number.isRequired, + + /** + * Handles pagination + */ + onPageChange: PropTypes.func.isRequired, + + /** + * Handles feedback when successfully closed + */ + feedbackClosed: PropTypes.func.isRequired }; state = {}; @@ -53,6 +63,9 @@ export default class FeedbackList extends Component { ); }; + /** + * When page number is clicked + */ onPageChange = e => { let currentUrlParams = new URLSearchParams(window.location.search); currentUrlParams.set("page", e.target.id); diff --git a/frontend/src/components/common/FeedbackTicket.jsx b/frontend/src/components/common/FeedbackTicket.jsx index 46ad3dec..66a7d558 100644 --- a/frontend/src/components/common/FeedbackTicket.jsx +++ b/frontend/src/components/common/FeedbackTicket.jsx @@ -7,7 +7,8 @@ import { DEFAULT_USERNAME, DEFAULT_USER_ID } from "../../constants/user"; import { getCurrentUser } from "../../services/http/authService"; import { postFeedbackMessage, - updateSeenAt + updateSeenAt, + closeFeedback } from "../../services/http/feedbackService"; import { connectSocket } from "../../services/socket/base"; import { @@ -41,6 +42,9 @@ export default class FeedbackTicket extends Component { messages: [], inputMessage: "", latestAuthorName: "", + /** + * Current user (anonymous or logged in) + */ user: { name: "", id: "" @@ -51,6 +55,7 @@ export default class FeedbackTicket extends Component { * Checks if this client is the author of received chat message */ isAuthorCurrentClient: false, + isClosed: false, error: "" }; @@ -140,7 +145,11 @@ export default class FeedbackTicket extends Component { return false; }; + /** + * Sets states of necessary properties on mount + */ updateMountState = (socket, user, { feedback, messages }) => { + const { isClosed } = feedback; const lastMessage = messages[messages.length - 1]; const latestAuthorName = this.resolveAuthorName(lastMessage); this.setState({ @@ -156,6 +165,7 @@ export default class FeedbackTicket extends Component { user.name ), socket, + isClosed, error: "" }); }; @@ -215,6 +225,22 @@ export default class FeedbackTicket extends Component { this.setState({ error: err.message }); } + onCloseFeedback = () => { + const { id } = this.props.feedback; + closeFeedback(id) + .then(res => this.onCloseFeedbackSuccess()) + .catch(err => this.onCloseFeedbackError(err)); + }; + + onCloseFeedbackSuccess = () => { + this.setState({ isClosed: true }); + }; + + onCloseFeedbackError = err => { + alert(err); + window.location.reload(); + }; + onSendMessage = e => { e.preventDefault(); const { inputMessage, user } = this.state; @@ -260,7 +286,9 @@ export default class FeedbackTicket extends Component { messages, seen, isMessageSubmitting, - error + error, + isClosed, + user } = this.state; const { feedback } = this.props; return ( @@ -297,8 +325,8 @@ export default class FeedbackTicket extends Component { this.setState({ inputMessage: e.target.value })} /> @@ -312,6 +340,15 @@ export default class FeedbackTicket extends Component { src={send} /> + {user.id !== DEFAULT_USER_ID && ( +
+ {!isClosed && ( + + Close this ticket + + )} +
+ )}
); } diff --git a/frontend/src/components/common/TicketMessage.jsx b/frontend/src/components/common/TicketMessage.jsx index e2c587b6..e203c52c 100644 --- a/frontend/src/components/common/TicketMessage.jsx +++ b/frontend/src/components/common/TicketMessage.jsx @@ -34,6 +34,9 @@ export default class TicketMessage extends Component { totalMessages: PropTypes.number.isRequired }; + /** + * Used to output message on the left or the right of chat + */ getColumn = () => { const user = getCurrentUser(); const { userName } = this.props; diff --git a/frontend/src/pages/Feedbacks.jsx b/frontend/src/pages/Feedbacks.jsx index 0a462348..bef0feeb 100644 --- a/frontend/src/pages/Feedbacks.jsx +++ b/frontend/src/pages/Feedbacks.jsx @@ -14,17 +14,16 @@ export default class Feedbacks extends Component { itemsPerPage: 10 }; - getAllFeedbackRequest() { + componentDidMount() { getAllFeedback() .then(res => this.onGetFeedbackSuccess(res.result)) .catch(err => this.onGetFeedbackError(err)); } - componentDidMount() { - this.getAllFeedbackRequest(); - } - - calcTotalPages = res => { + /** + * Returns total pages based on number of feedbacks + */ + calculateTotalPages = res => { const { itemsPerPage, totalPages } = this.state; for (let i = 1; i <= Math.ceil(res.length / itemsPerPage); i++) { totalPages.push(i); @@ -32,6 +31,9 @@ export default class Feedbacks extends Component { return totalPages; }; + /** + * Checks if page passed to url is a valid number + */ validatePage = page => { if (typeof currentPage != "number") { return 1; @@ -43,7 +45,7 @@ export default class Feedbacks extends Component { const { page } = queryString.parse(this.props.location.search); this.setState({ feedbacks: res.reverse(), - totalPages: this.calcTotalPages(res) + totalPages: this.calculateTotalPages(res) }); this.onPageChange(this.validatePage(page)); }; @@ -54,6 +56,9 @@ export default class Feedbacks extends Component { newWindowLocation(FEEDBACK_ROUTE); }; + /** + * When feedback successfully closed + */ feedbackClosed = feedbackId => { const { currentFeedbacks } = this.state; const index = currentFeedbacks.findIndex(item => item.id === feedbackId); @@ -61,6 +66,9 @@ export default class Feedbacks extends Component { this.setState({ currentFeedbacks }); }; + /** + * Handles pagination + */ onPageChange = page => { const { feedbacks, itemsPerPage } = this.state; const indexOfLastFeedback = page * itemsPerPage; @@ -73,12 +81,13 @@ export default class Feedbacks extends Component { }; render() { + const { currentFeedbacks, totalPages, currentPage } = this.state; return ( diff --git a/frontend/src/styles/common/feedbackTicket.css b/frontend/src/styles/common/feedbackTicket.css index c30d19c5..ee2b45f0 100644 --- a/frontend/src/styles/common/feedbackTicket.css +++ b/frontend/src/styles/common/feedbackTicket.css @@ -60,3 +60,19 @@ margin-top: 2.5px; margin-right: 10px; } + +.close-ticket-container { + margin-top: 15px; +} + +.close-ticket-container .close-ticket { + font-family: SourceSansPro; + font-size: 14px; + text-decoration: underline !important; +} + +.close-ticket-container .close-ticket:hover { + cursor: pointer; + color: #555454 !important; + text-decoration: underline !important; +} From 05274f27ac9982ca787542130ada13f899d85ea6 Mon Sep 17 00:00:00 2001 From: fvedad Date: Sun, 8 Sep 2019 14:12:36 +0200 Subject: [PATCH 14/72] Add token to sign up --- backend/example.env | 3 ++ backend/src/routes/auth/sign-up.js | 3 ++ .../routes/feedback/feedback.messages.post.js | 1 + frontend/package-lock.json | 46 +++++++------------ .../src/components/common/FeedbackTicket.jsx | 19 +++++++- frontend/src/components/common/SignUpForm.jsx | 22 +++++++-- .../src/components/common/ui/form/Form.jsx | 1 - frontend/src/constants/form/labels/input.js | 1 + frontend/src/constants/form/names/input.js | 1 + frontend/src/services/http/authService.js | 3 +- frontend/src/styles/common/feedbackTicket.css | 2 + 11 files changed, 64 insertions(+), 38 deletions(-) diff --git a/backend/example.env b/backend/example.env index 9ce99ff5..8491b639 100644 --- a/backend/example.env +++ b/backend/example.env @@ -20,6 +20,9 @@ COOKIE_S=ayQuuuIcXSa7I6HSWK1H // jwt secret used for auth token generation JWT_SECRET=godpg75uFXxstUIwp2Ca +// jwt secret used for new user registration +JWT_SECRET_SIGNUP=5hvxxTiKbTaeEHNgqymmJP3DvKEJgT + // app modes (development, production) // development - uses PGDATABASE // production - uses DATABASE_URL diff --git a/backend/src/routes/auth/sign-up.js b/backend/src/routes/auth/sign-up.js index 2c31bec7..f943ac22 100644 --- a/backend/src/routes/auth/sign-up.js +++ b/backend/src/routes/auth/sign-up.js @@ -1,8 +1,11 @@ +import jwt from 'jsonwebtoken'; + export default ({ models }) => { const { User } = models; return async (req, res, next) => { let reqUser = req.body; try { + jwt.verify(reqUser.key, process.env.JWT_SECRET_SIGNUP); const { user, token } = await User.insert(reqUser); res .header('x-auth', token) diff --git a/backend/src/routes/feedback/feedback.messages.post.js b/backend/src/routes/feedback/feedback.messages.post.js index dc95c678..a97c5b9f 100644 --- a/backend/src/routes/feedback/feedback.messages.post.js +++ b/backend/src/routes/feedback/feedback.messages.post.js @@ -10,6 +10,7 @@ export default ({ models }) => { const feedback = await Feedback.findById(feedbackId); if (!feedback) throw new Error(`Feedback with id ${feedbackId} does not exist`); + if (feedback.isClosed) throw new Error('This ticket is closed'); let user = null; if (userId) { user = await User.findById(userId); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 287698a3..675bba64 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4539,11 +4539,6 @@ "is-obj": "^1.0.0" } }, - "dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" - }, "dotenv-expand": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", @@ -7131,8 +7126,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -7169,8 +7163,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -7179,8 +7172,7 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7283,8 +7275,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7294,7 +7285,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7307,20 +7297,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7337,7 +7324,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7410,8 +7396,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -7421,7 +7406,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7497,8 +7481,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -7528,7 +7511,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7546,7 +7528,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7585,13 +7566,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } } @@ -10882,6 +10861,13 @@ "webpack-dev-server": "3.2.1", "webpack-manifest-plugin": "2.0.4", "workbox-webpack-plugin": "4.2.0" + }, + "dependencies": { + "dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" + } } }, "react-smooth-dnd": { diff --git a/frontend/src/components/common/FeedbackTicket.jsx b/frontend/src/components/common/FeedbackTicket.jsx index 66a7d558..4f2f88c8 100644 --- a/frontend/src/components/common/FeedbackTicket.jsx +++ b/frontend/src/components/common/FeedbackTicket.jsx @@ -38,6 +38,12 @@ export default class FeedbackTicket extends Component { messages: PropTypes.array.isRequired }; + constructor(props) { + super(props); + this.textInput = React.createRef(); + this.focus = this.focus.bind(this); + } + state = { messages: [], inputMessage: "", @@ -274,12 +280,20 @@ export default class FeedbackTicket extends Component { inputMessage: "" }); emitMessage(this.props.feedback.id, messages); + this.focus(); }; onPostFeedbackMessageError = err => { this.setState({ error: err.message, isMessageSubmitting: false }); }; + /** + * focus on message input field + */ + focus() { + this.textInput.current.focus(); + } + render() { const { inputMessage, @@ -329,6 +343,7 @@ export default class FeedbackTicket extends Component { disabled={isClosed || isMessageSubmitting} placeholder="Type a message..." onChange={e => this.setState({ inputMessage: e.target.value })} + ref={this.textInput} /> {!isClosed && ( - + )}
)} diff --git a/frontend/src/components/common/SignUpForm.jsx b/frontend/src/components/common/SignUpForm.jsx index f7915fcf..b896b1b5 100644 --- a/frontend/src/components/common/SignUpForm.jsx +++ b/frontend/src/components/common/SignUpForm.jsx @@ -8,14 +8,16 @@ import { EMAIL_LABEL, NAME_LABEL, PASSWORD_LABEL, - SURNAME_LABEL + SURNAME_LABEL, + KEY_LABEL } from "../../constants/form/labels/input"; import { CONFIRM_PASSWORD, EMAIL, NAME, SIGNUP_PASSWORD, - SURNAME + SURNAME, + KEY } from "../../constants/form/names/input"; import Form from "./ui/form/Form"; import LoadingSpinner from "./ui/LoadingSpinner"; @@ -40,7 +42,8 @@ export default class SignUpForm extends Form { surname: "", email: "", signUpPassword: "", - confirmPassword: "" + confirmPassword: "", + key: "" }, errors: {} }; @@ -63,7 +66,10 @@ export default class SignUpForm extends Form { .options({ language: { any: { allowOnly: "must match password" } } }) - .label(CONFIRM_PASSWORD_LABEL) + .label(CONFIRM_PASSWORD_LABEL), + key: Joi.string() + .required() + .label(KEY_LABEL) }; onSubmit = e => { @@ -118,6 +124,14 @@ export default class SignUpForm extends Form { "password", true )} + {this.renderInput( + KEY, + KEY_LABEL, + KEY_LABEL, + isSubmitting, + "password", + true + )} {isSubmitting && ( { return res; }); diff --git a/frontend/src/styles/common/feedbackTicket.css b/frontend/src/styles/common/feedbackTicket.css index ee2b45f0..ffb9a870 100644 --- a/frontend/src/styles/common/feedbackTicket.css +++ b/frontend/src/styles/common/feedbackTicket.css @@ -69,6 +69,8 @@ font-family: SourceSansPro; font-size: 14px; text-decoration: underline !important; + border: 0; + background-color: transparent; } .close-ticket-container .close-ticket:hover { From 92ddd5e8359aae6981dee4ef9cc4135507e4b6f2 Mon Sep 17 00:00:00 2001 From: fvedad Date: Sun, 8 Sep 2019 14:52:45 +0200 Subject: [PATCH 15/72] Add migrations --- backend/config/config.js | 19 + backend/example.env | 3 + .../migrations/20190908122605-create-user.js | 69 +++ .../20190908123238-create-answer.js | 36 ++ .../20190908123432-create-feedback.js | 54 +++ .../20190908123818-create-message.js | 40 ++ .../20190908123823-create-poll-template.js | 68 +++ .../migrations/20190908123829-create-poll.js | 44 ++ backend/models/answer.js | 14 + backend/models/feedback.js | 17 + backend/models/index.js | 37 ++ backend/models/message.js | 14 + backend/models/poll.js | 20 + backend/models/polltemplate.js | 18 + backend/models/user.js | 20 + backend/package-lock.json | 403 +++++++++++++++--- backend/package.json | 1 + 17 files changed, 809 insertions(+), 68 deletions(-) create mode 100644 backend/config/config.js create mode 100644 backend/migrations/20190908122605-create-user.js create mode 100644 backend/migrations/20190908123238-create-answer.js create mode 100644 backend/migrations/20190908123432-create-feedback.js create mode 100644 backend/migrations/20190908123818-create-message.js create mode 100644 backend/migrations/20190908123823-create-poll-template.js create mode 100644 backend/migrations/20190908123829-create-poll.js create mode 100644 backend/models/answer.js create mode 100644 backend/models/feedback.js create mode 100644 backend/models/index.js create mode 100644 backend/models/message.js create mode 100644 backend/models/poll.js create mode 100644 backend/models/polltemplate.js create mode 100644 backend/models/user.js diff --git a/backend/config/config.js b/backend/config/config.js new file mode 100644 index 00000000..840f64cd --- /dev/null +++ b/backend/config/config.js @@ -0,0 +1,19 @@ +require('dotenv').config(); + +// properties matched with NODE_ENV environment variable +module.exports = { + dev: { + username: process.env.DB_USERNAME, + password: process.env.DB_USER_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + dialect: 'postgres', + }, + production: { + username: process.env.DB_USERNAME, + password: process.env.DB_USER_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + dialect: 'postgres', + }, +}; diff --git a/backend/example.env b/backend/example.env index 8491b639..6fb856a1 100644 --- a/backend/example.env +++ b/backend/example.env @@ -28,6 +28,9 @@ JWT_SECRET_SIGNUP=5hvxxTiKbTaeEHNgqymmJP3DvKEJgT // production - uses DATABASE_URL MODE=development +// environment (dev or production) +NODE_ENV=dev + // properties for feedback destination email EMAIL_FEEDBACK=email@email.com EMAIL_FEEDBACK_PW=password diff --git a/backend/migrations/20190908122605-create-user.js b/backend/migrations/20190908122605-create-user.js new file mode 100644 index 00000000..243505bd --- /dev/null +++ b/backend/migrations/20190908122605-create-user.js @@ -0,0 +1,69 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that name is empty', + }, + }, + }, + surname: { + type: Sequelize.STRING, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that surname is empty', + }, + }, + }, + email: { + type: Sequelize.STRING, + unique: true, + allowNull: false, + validate: { + isEmail: { + msg: 'Email address must be valid', + }, + notEmpty: { + msg: 'Unexpected that email is empty', + }, + }, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + validate: { + len: { + msg: 'Password must be at least 8 characters long', + args: 8, + }, + }, + }, + tokens: { + type: Sequelize.ARRAY(Sequelize.STRING(800)), + defaultValue: [], + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Users'); + }, +}; diff --git a/backend/migrations/20190908123238-create-answer.js b/backend/migrations/20190908123238-create-answer.js new file mode 100644 index 00000000..1a0f3bf3 --- /dev/null +++ b/backend/migrations/20190908123238-create-answer.js @@ -0,0 +1,36 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Answers', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + content: { + type: Sequelize.JSONB, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that content field is empty', + }, + notNull: { + msg: 'Answers must be provided', + }, + }, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Answers'); + }, +}; diff --git a/backend/migrations/20190908123432-create-feedback.js b/backend/migrations/20190908123432-create-feedback.js new file mode 100644 index 00000000..cc65a0c6 --- /dev/null +++ b/backend/migrations/20190908123432-create-feedback.js @@ -0,0 +1,54 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Feedbacks', { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + primaryKey: true, + allowNull: false, + validate: { + notNull: true, + }, + }, + isClosed: { + type: Sequelize.BOOLEAN, + defaultValue: false, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that isOpen field is empty', + }, + notNull: { + msg: 'Field isOpen must be set', + }, + }, + }, + anonymLastSeenAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + allowNull: false, + validate: { + notNull: { + msg: 'Field anonymSeenAt must be set', + }, + }, + }, + userLastSeenAt: { + type: Sequelize.DATE, + defaultValue: null, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Feedbacks'); + }, +}; diff --git a/backend/migrations/20190908123818-create-message.js b/backend/migrations/20190908123818-create-message.js new file mode 100644 index 00000000..5625c1c6 --- /dev/null +++ b/backend/migrations/20190908123818-create-message.js @@ -0,0 +1,40 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Messages', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + text: { + type: Sequelize.STRING(1000), + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that text field is empty', + }, + notNull: { + msg: 'Message text must be provided', + }, + len: { + args: [1, 1000], + msg: 'Messages greater than 1000 characters are not allowed', + }, + }, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Messages'); + }, +}; diff --git a/backend/migrations/20190908123823-create-poll-template.js b/backend/migrations/20190908123823-create-poll-template.js new file mode 100644 index 00000000..b83def11 --- /dev/null +++ b/backend/migrations/20190908123823-create-poll-template.js @@ -0,0 +1,68 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('PollTemplates', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + title: { + type: Sequelize.STRING, + unique: true, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that title is empty', + }, + notNull: { + msg: 'Title must be provided', + }, + }, + }, + description: { + type: Sequelize.STRING, + allowNull: false, + validate: { + notEmpty: { + msg: 'Unexpected that description is empty', + }, + notNull: { + msg: 'Description must be provided', + }, + }, + }, + questions: { + type: Sequelize.JSONB, + allowNull: false, + notEmpty: true, + validate: { + notEmpty: { + msg: 'Unexpected that questions field is empty', + }, + notNull: { + msg: 'Questions must be provided for poll template', + }, + }, + }, + isDraft: { + type: Sequelize.BOOLEAN, + allowNull: false, + notEmpty: true, + defaultValue: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('PollTemplates'); + }, +}; diff --git a/backend/migrations/20190908123829-create-poll.js b/backend/migrations/20190908123829-create-poll.js new file mode 100644 index 00000000..1c7d6742 --- /dev/null +++ b/backend/migrations/20190908123829-create-poll.js @@ -0,0 +1,44 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Polls', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + entity: { + type: Sequelize.STRING, + allowNull: false, + }, + description: { + type: Sequelize.TEXT, + allowNull: false, + }, + locked: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + maxNumAnswers: { + type: Sequelize.INTEGER, + allowNull: false, + }, + numAnswers: { + type: Sequelize.INTEGER, + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Polls'); + }, +}; diff --git a/backend/models/answer.js b/backend/models/answer.js new file mode 100644 index 00000000..8242aabd --- /dev/null +++ b/backend/models/answer.js @@ -0,0 +1,14 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const Answer = sequelize.define( + 'Answer', + { + content: DataTypes.JSONB, + }, + {} + ); + Answer.associate = function(models) { + Answer.belongsTo(models.Poll); + }; + return Answer; +}; diff --git a/backend/models/feedback.js b/backend/models/feedback.js new file mode 100644 index 00000000..5c1d6fbb --- /dev/null +++ b/backend/models/feedback.js @@ -0,0 +1,17 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const Feedback = sequelize.define( + 'Feedback', + { + id: DataTypes.UUID, + isClosed: DataTypes.BOOLEAN, + anonymLastSeenAt: DataTypes.DATE, + userLastSeenAt: DataTypes.DATE, + }, + {} + ); + Feedback.associate = function(models) { + Feedback.hasMany(models.Message, { onDelete: 'CASCADE' }); + }; + return Feedback; +}; diff --git a/backend/models/index.js b/backend/models/index.js new file mode 100644 index 00000000..c1a3d6d5 --- /dev/null +++ b/backend/models/index.js @@ -0,0 +1,37 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Sequelize = require('sequelize'); +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; +const config = require(__dirname + '/../config/config.json')[env]; +const db = {}; + +let sequelize; +if (config.use_env_variable) { + sequelize = new Sequelize(process.env[config.use_env_variable], config); +} else { + sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +fs + .readdirSync(__dirname) + .filter(file => { + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + }) + .forEach(file => { + const model = sequelize['import'](path.join(__dirname, file)); + db[model.name] = model; + }); + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; diff --git a/backend/models/message.js b/backend/models/message.js new file mode 100644 index 00000000..863e9311 --- /dev/null +++ b/backend/models/message.js @@ -0,0 +1,14 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const Message = sequelize.define( + 'Message', + { + text: DataTypes.STRING, + }, + {} + ); + Message.associate = function(models) { + Message.belongsTo(models.User); + }; + return Message; +}; diff --git a/backend/models/poll.js b/backend/models/poll.js new file mode 100644 index 00000000..2172abcf --- /dev/null +++ b/backend/models/poll.js @@ -0,0 +1,20 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const Poll = sequelize.define( + 'Poll', + { + entity: DataTypes.STRING, + description: DataTypes.TEXT, + locked: DataTypes.BOOLEAN, + maxNumAnswers: DataTypes.INTEGER, + numAnswers: DataTypes.INTEGER, + }, + {} + ); + Poll.associate = function(models) { + Poll.hasMany(models.Answer, { onDelete: 'CASCADE' }); + Poll.belongsTo(models.PollTemplate); + Poll.belongsTo(models.User); + }; + return Poll; +}; diff --git a/backend/models/polltemplate.js b/backend/models/polltemplate.js new file mode 100644 index 00000000..76ba0a5b --- /dev/null +++ b/backend/models/polltemplate.js @@ -0,0 +1,18 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const PollTemplate = sequelize.define( + 'PollTemplate', + { + title: DataTypes.STRING, + description: DataTypes.STRING, + questions: DataTypes.JSONB, + isDraft: DataTypes.BOOLEAN, + }, + {} + ); + PollTemplate.associate = function(models) { + PollTemplate.hasMany(models.Poll, { onDelete: 'CASCADE' }); + PollTemplate.belongsTo(models.User); + }; + return PollTemplate; +}; diff --git a/backend/models/user.js b/backend/models/user.js new file mode 100644 index 00000000..295377b9 --- /dev/null +++ b/backend/models/user.js @@ -0,0 +1,20 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const User = sequelize.define( + 'User', + { + name: DataTypes.STRING, + surname: DataTypes.STRING, + email: DataTypes.STRING, + password: DataTypes.STRING, + tokens: DataTypes.ARRAY, + }, + {} + ); + User.associate = function(models) { + User.hasMany(models.PollTemplate); + User.hasMany(models.Poll); + User.hasMany(models.Message); + }; + return User; +}; diff --git a/backend/package-lock.json b/backend/package-lock.json index 79ed2637..2954acd0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1350,7 +1350,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -1555,6 +1554,22 @@ "babel-plugin-jest-hoist": "^24.6.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -1891,8 +1906,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { "version": "1.0.30000984", @@ -1992,6 +2006,19 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + } + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -2070,7 +2097,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2078,8 +2104,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { "version": "1.0.8", @@ -2092,8 +2117,7 @@ "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" }, "commondir": { "version": "1.0.1", @@ -2121,6 +2145,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "configstore": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", @@ -2212,8 +2245,7 @@ "core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" }, "core-js-compat": { "version": "3.1.4", @@ -2297,6 +2329,15 @@ "cssom": "~0.3.6" } }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2346,8 +2387,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -2510,6 +2550,17 @@ "safe-buffer": "^5.0.1" } }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2521,6 +2572,11 @@ "integrity": "sha512-jasjtY5RUy/TOyiUYM2fb4BDaPZfm6CXRFeJDMfFsXYADGxUN49RBqtgB7EL2RmJXeIRUk9lM1U6A5yk2YJMPQ==", "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2663,6 +2719,46 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", + "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.51" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2719,6 +2815,15 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "exec-sh": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", @@ -3058,7 +3163,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -3109,6 +3213,16 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-minipass": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", @@ -3148,8 +3262,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3170,14 +3283,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3192,20 +3303,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3322,8 +3430,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3335,7 +3442,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3350,7 +3456,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3358,14 +3463,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3384,7 +3487,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3465,8 +3567,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3478,7 +3579,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3564,8 +3664,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3601,7 +3700,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3621,7 +3719,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3665,14 +3762,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -3806,8 +3901,7 @@ "graceful-fs": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", - "dev": true + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" }, "growly": { "version": "1.3.0", @@ -4282,6 +4376,11 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -4873,6 +4972,18 @@ } } }, + "js-beautify": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz", + "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==", + "requires": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "~0.5.1", + "nopt": "~4.0.1" + } + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -4972,6 +5083,14 @@ } } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -5101,7 +5220,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -5177,7 +5295,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -5186,11 +5303,18 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -5250,6 +5374,21 @@ "p-is-promise": "^2.0.0" } }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -5442,6 +5581,11 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -5844,7 +5988,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -5853,7 +5996,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -5867,8 +6009,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "4.0.1", @@ -5945,8 +6086,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -5968,8 +6108,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -6181,6 +6320,11 @@ "sisteransi": "^1.0.0" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -6193,8 +6337,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.2.0", @@ -6529,20 +6672,17 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -6738,6 +6878,102 @@ } } }, + "sequelize-cli": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.1.tgz", + "integrity": "sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q==", + "requires": { + "bluebird": "^3.5.3", + "cli-color": "^1.4.0", + "fs-extra": "^7.0.1", + "js-beautify": "^1.8.8", + "lodash": "^4.17.5", + "resolve": "^1.5.0", + "umzug": "^2.1.0", + "yargs": "^13.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "sequelize-pool": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", @@ -6813,6 +7049,11 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -7413,6 +7654,15 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -7542,6 +7792,11 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", + "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==" + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -7585,6 +7840,15 @@ } } }, + "umzug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", + "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", + "requires": { + "babel-runtime": "^6.23.0", + "bluebird": "^3.5.3" + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -7665,6 +7929,11 @@ "crypto-random-string": "^1.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7914,8 +8183,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.3", @@ -8041,8 +8309,7 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { "version": "3.0.3", diff --git a/backend/package.json b/backend/package.json index 0e766b61..33e820d0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,6 +39,7 @@ "pg": "^7.9.0", "pg-hstore": "^2.3.2", "sequelize": "^5.1.0", + "sequelize-cli": "^5.5.1", "sinon": "^7.3.2", "socket.io": "^2.2.0", "supertest": "^4.0.2" From 989bb2004c3ab145709f1b31e39a8879bd3d3d7a Mon Sep 17 00:00:00 2001 From: bjusufbe Date: Wed, 4 Sep 2019 14:04:49 +0200 Subject: [PATCH 16/72] Removed passing app creds from Jenkins credentials, using k8s secret for that --- ops/Jenkinsfile | 42 ++++++++++++++---------------- ops/helm/templates/deployment.yaml | 10 +++++-- ops/helm/values.deploy.yaml | 3 +-- ops/helm/values.yaml | 3 +-- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/ops/Jenkinsfile b/ops/Jenkinsfile index 49d96f4c..7bafb114 100644 --- a/ops/Jenkinsfile +++ b/ops/Jenkinsfile @@ -51,28 +51,26 @@ spec: stage('Deploy helm chart') { steps { - withCredentials([string(credentialsId: 'JWT_SECRET', variable: 'JWT_SECRET'), string(credentialsId: 'COOKIE_S', variable: 'COOKIE_S')]) { - container('helm') { - sh ''' - #!/bin/bash - - if [[ "$ENVIRONMENT" == "deploy" ]]; then - values_arg="-f ops/helm/values.deploy.yaml" - fi; - helm upgrade aa-${ENVIRONMENT} \ - --recreate-pods \ - --namespace aa \ - --install $values_arg \ - --set image.tag=${IMAGE_TAG} \ - --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ - --set ingress.hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ - --set config.jwtSecret=${JWT_SECRET} \ - --set config.cookieS=${COOKIE_S} \ - ops/helm/ - ''' - } + container('helm') { + sh ''' + #!/bin/bash + + if [[ "$ENVIRONMENT" == "deploy" ]]; then + values_arg="-f ops/helm/values.deploy.yaml" + fi; + helm upgrade aa-${ENVIRONMENT} \ + --recreate-pods \ + --namespace aa \ + --install $values_arg \ + --set image.tag=${IMAGE_TAG} \ + --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ + --set ingress.hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ + --set config.jwtSecret=${JWT_SECRET} \ + --set config.cookieS=${COOKIE_S} \ + ops/helm/ + ''' } } } diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index f7c7589e..dee7e9c7 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -66,9 +66,15 @@ spec: - name: PORT value: "3000" - name: JWT_SECRET - value: {{ .Values.config.jwtSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: jwt_secret - name: COOKIE_S - value: {{ .Values.config.cookieS }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: cookie_s - name: SALT_ROUNDS value: "10" livenessProbe: diff --git a/ops/helm/values.deploy.yaml b/ops/helm/values.deploy.yaml index 93b3a386..6ab465fd 100644 --- a/ops/helm/values.deploy.yaml +++ b/ops/helm/values.deploy.yaml @@ -17,6 +17,5 @@ config: dbHost: atlanters-anonymous-pg-deploy-postgresql dbSecret: atlanters-anonymous-pg-deploy-postgresql smtpSecret: atlanters-anonymous-smtp-deploy - jwtSecret: jwt-secret-deploy - cookieS: cookie-s-deploy + appSecret: atlanters-anonymous-app-deploy diff --git a/ops/helm/values.yaml b/ops/helm/values.yaml index 38005b62..bad4542e 100644 --- a/ops/helm/values.yaml +++ b/ops/helm/values.yaml @@ -31,8 +31,7 @@ config: dbHost: atlanters-anonymous-pg-postgresql dbSecret: atlanters-anonymous-pg-postgresql smtpSecret: atlanters-anonymous-smtp - jwtSecret: jwt-secret - cookieS: cookie-s + appSecret: atlanters-anonymous-app resources: {} From fce684ac7ec9633d75a94faa55b89697b4c64102 Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Mon, 9 Sep 2019 13:41:45 +0200 Subject: [PATCH 17/72] Level develop ops with deploy ops --- ops/Jenkinsfile | 42 ++++++++++++++---------------- ops/helm/templates/deployment.yaml | 10 +++++-- ops/helm/values.deploy.yaml | 10 +++---- ops/helm/values.yaml | 11 ++++---- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/ops/Jenkinsfile b/ops/Jenkinsfile index 49d96f4c..7bafb114 100644 --- a/ops/Jenkinsfile +++ b/ops/Jenkinsfile @@ -51,28 +51,26 @@ spec: stage('Deploy helm chart') { steps { - withCredentials([string(credentialsId: 'JWT_SECRET', variable: 'JWT_SECRET'), string(credentialsId: 'COOKIE_S', variable: 'COOKIE_S')]) { - container('helm') { - sh ''' - #!/bin/bash - - if [[ "$ENVIRONMENT" == "deploy" ]]; then - values_arg="-f ops/helm/values.deploy.yaml" - fi; - helm upgrade aa-${ENVIRONMENT} \ - --recreate-pods \ - --namespace aa \ - --install $values_arg \ - --set image.tag=${IMAGE_TAG} \ - --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ - --set ingress.hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].hosts[0]=${ENDPOINT} \ - --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ - --set config.jwtSecret=${JWT_SECRET} \ - --set config.cookieS=${COOKIE_S} \ - ops/helm/ - ''' - } + container('helm') { + sh ''' + #!/bin/bash + + if [[ "$ENVIRONMENT" == "deploy" ]]; then + values_arg="-f ops/helm/values.deploy.yaml" + fi; + helm upgrade aa-${ENVIRONMENT} \ + --recreate-pods \ + --namespace aa \ + --install $values_arg \ + --set image.tag=${IMAGE_TAG} \ + --set ingress.annotations."nginx\\.ingress\\.kubernetes\\.io/whitelist-source-range"=${IP_WHITELIST} \ + --set ingress.hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].hosts[0]=${ENDPOINT} \ + --set ingress.tls[0].secretName=aa-${ENVIRONMENT} \ + --set config.jwtSecret=${JWT_SECRET} \ + --set config.cookieS=${COOKIE_S} \ + ops/helm/ + ''' } } } diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index f7c7589e..dee7e9c7 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -66,9 +66,15 @@ spec: - name: PORT value: "3000" - name: JWT_SECRET - value: {{ .Values.config.jwtSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: jwt_secret - name: COOKIE_S - value: {{ .Values.config.cookieS }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: cookie_s - name: SALT_ROUNDS value: "10" livenessProbe: diff --git a/ops/helm/values.deploy.yaml b/ops/helm/values.deploy.yaml index 93b3a386..33358d1c 100644 --- a/ops/helm/values.deploy.yaml +++ b/ops/helm/values.deploy.yaml @@ -9,14 +9,12 @@ ingress: hosts: - atlantersanonymous.dev.example.com tls: - - secretName: atlanters-anonymous-deploy - hosts: - - atlantersanonymous.dev.example.com + - secretName: atlanters-anonymous-deploy + hosts: + - atlantersanonymous.dev.example.com config: dbHost: atlanters-anonymous-pg-deploy-postgresql dbSecret: atlanters-anonymous-pg-deploy-postgresql smtpSecret: atlanters-anonymous-smtp-deploy - jwtSecret: jwt-secret-deploy - cookieS: cookie-s-deploy - + appSecret: atlanters-anonymous-app-deploy diff --git a/ops/helm/values.yaml b/ops/helm/values.yaml index 38005b62..27fc36bc 100644 --- a/ops/helm/values.yaml +++ b/ops/helm/values.yaml @@ -19,20 +19,19 @@ ingress: nginx.ingress.kubernetes.io/whitelist-source-range: 0.0.0.0/0 nginx.ingress.kubernetes.io/enable-access-log: "false" paths: - - / + - / hosts: - atlantersanonymous.example.com tls: - - secretName: atlanters-anonymous - hosts: - - atlantersanonymous.example.com + - secretName: atlanters-anonymous + hosts: + - atlantersanonymous.example.com config: dbHost: atlanters-anonymous-pg-postgresql dbSecret: atlanters-anonymous-pg-postgresql smtpSecret: atlanters-anonymous-smtp - jwtSecret: jwt-secret - cookieS: cookie-s + appSecret: atlanters-anonymous-app resources: {} From 2d4d792c6684385b3fca5917dc7284e98adacbfd Mon Sep 17 00:00:00 2001 From: dduvnjak Date: Mon, 9 Sep 2019 16:55:22 +0200 Subject: [PATCH 18/72] Add new secrets --- ops/helm/templates/deployment.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index dee7e9c7..c364305c 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -70,11 +70,26 @@ spec: secretKeyRef: name: {{ .Values.config.appSecret }} key: jwt_secret + - name: JWT_SECRET_SIGNUP + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: jwt_secret_signup - name: COOKIE_S valueFrom: secretKeyRef: name: {{ .Values.config.appSecret }} key: cookie_s + - name: GITHUB_APP_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: github_app_secret + - name: GITHUB_APP_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: github_app_client_id - name: SALT_ROUNDS value: "10" livenessProbe: From 55d106961a4395c52356a7eea0edb4cf9bca55b1 Mon Sep 17 00:00:00 2001 From: dduvnjak Date: Tue, 10 Sep 2019 15:00:00 +0200 Subject: [PATCH 19/72] Put SALT_ROUDNS into secrets --- ops/helm/templates/deployment.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ops/helm/templates/deployment.yaml b/ops/helm/templates/deployment.yaml index c364305c..b714ae06 100644 --- a/ops/helm/templates/deployment.yaml +++ b/ops/helm/templates/deployment.yaml @@ -91,7 +91,10 @@ spec: name: {{ .Values.config.appSecret }} key: github_app_client_id - name: SALT_ROUNDS - value: "10" + valueFrom: + secretKeyRef: + name: {{ .Values.config.appSecret }} + key: salt_rounds livenessProbe: httpGet: path: / From fcd5c98213eba4444e1d0754e9da6ce4bb180afb Mon Sep 17 00:00:00 2001 From: Vedad Fejzagic Date: Tue, 10 Sep 2019 17:00:32 +0200 Subject: [PATCH 20/72] Add initial user --- backend/example.env | 3 --- backend/src/init/models.js | 10 ++++++++++ backend/src/routes/auth/sign-up.js | 3 --- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/example.env b/backend/example.env index 6fb856a1..483a3a80 100644 --- a/backend/example.env +++ b/backend/example.env @@ -20,9 +20,6 @@ COOKIE_S=ayQuuuIcXSa7I6HSWK1H // jwt secret used for auth token generation JWT_SECRET=godpg75uFXxstUIwp2Ca -// jwt secret used for new user registration -JWT_SECRET_SIGNUP=5hvxxTiKbTaeEHNgqymmJP3DvKEJgT - // app modes (development, production) // development - uses PGDATABASE // production - uses DATABASE_URL diff --git a/backend/src/init/models.js b/backend/src/init/models.js index 4c8c4dd7..fe5e0cb1 100644 --- a/backend/src/init/models.js +++ b/backend/src/init/models.js @@ -9,6 +9,7 @@ export default async (sequelize, eraseDatabaseOnSync) => { } catch (error) { console.log(error); } + await createFirstUser(models); if (eraseDatabaseOnSync) { await createUsersWithMessages(models); } @@ -16,6 +17,15 @@ export default async (sequelize, eraseDatabaseOnSync) => { return models; }; +const createFirstUser = async models => { + await models.User.create({ + name: 'Admin', + surname: 'Admin', + email: 'atlantersanonymous@atlantbh.com', + password: '$2b$10$z186VQxBAHCxS9Ry6yJLJORMMHSgKfglSYz1wq/tgw0hw/tp4I7Zu', + }); +}; + const createUsersWithMessages = async models => { const user = await models.User.create({ email: 'veda_df@dfasfasa.com', diff --git a/backend/src/routes/auth/sign-up.js b/backend/src/routes/auth/sign-up.js index f943ac22..2c31bec7 100644 --- a/backend/src/routes/auth/sign-up.js +++ b/backend/src/routes/auth/sign-up.js @@ -1,11 +1,8 @@ -import jwt from 'jsonwebtoken'; - export default ({ models }) => { const { User } = models; return async (req, res, next) => { let reqUser = req.body; try { - jwt.verify(reqUser.key, process.env.JWT_SECRET_SIGNUP); const { user, token } = await User.insert(reqUser); res .header('x-auth', token) From 1670085b99b8e84cfa45c6b530a229cb75163e00 Mon Sep 17 00:00:00 2001 From: fvedad Date: Tue, 10 Sep 2019 21:46:51 +0200 Subject: [PATCH 21/72] Protect sign up route, add change password functionality --- backend/src/models/user.js | 2 +- frontend/src/App.js | 7 +- .../components/common/ModifyAccountForm.jsx | 105 ++++++++++++++++++ frontend/src/components/common/Navbar.jsx | 15 ++- frontend/src/components/common/SignUpForm.jsx | 22 +--- .../src/components/common/ui/form/Form.jsx | 15 ++- frontend/src/constants/form/labels/button.js | 1 + frontend/src/constants/form/labels/input.js | 3 +- frontend/src/constants/form/names/input.js | 3 +- frontend/src/constants/routes.js | 5 +- frontend/src/pages/Account.jsx | 47 ++++++++ frontend/src/pages/Feedbacks.jsx | 2 +- frontend/src/pages/SignUp.jsx | 6 +- frontend/src/services/http/authService.js | 14 ++- frontend/src/styles/common/signUpForm.css | 4 - 15 files changed, 205 insertions(+), 46 deletions(-) create mode 100644 frontend/src/components/common/ModifyAccountForm.jsx create mode 100644 frontend/src/pages/Account.jsx diff --git a/backend/src/models/user.js b/backend/src/models/user.js index 2f4724c0..bacf2a56 100644 --- a/backend/src/models/user.js +++ b/backend/src/models/user.js @@ -154,7 +154,7 @@ class User extends Sequelize.Model { } if (password) { User.checkPasswordValid(reqUser.password); - if (!User.isPasswordHashProper(password, userToUpdate.password)) + if (await User.isPasswordHashProper(password, userToUpdate.password)) throw new Error(`Password matches the one currently in use`); password = await User.getPasswordHash(password); } diff --git a/frontend/src/App.js b/frontend/src/App.js index 31d8eae6..212ae66d 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -8,13 +8,15 @@ import { FEEDBACK_ROUTE_PARAMS, NOT_FOUND_ROUTE, SIGNIN_ROUTE, - SIGNUP_ROUTE + SIGNUP_ROUTE, + ACCOUNT_ROUTE } from "./constants/routes"; import Feedback from "./pages/Feedback"; import Feedbacks from "./pages/Feedbacks"; import NotFound from "./pages/NotFound"; import SignIn from "./pages/SignIn"; import SignUp from "./pages/SignUp"; +import Account from "./pages/Account"; import { getCurrentUser } from "./services/http/authService"; class App extends Component { @@ -32,7 +34,8 @@ class App extends Component { {user && }
- + + diff --git a/frontend/src/components/common/ModifyAccountForm.jsx b/frontend/src/components/common/ModifyAccountForm.jsx new file mode 100644 index 00000000..2c94f2af --- /dev/null +++ b/frontend/src/components/common/ModifyAccountForm.jsx @@ -0,0 +1,105 @@ +import Joi from "joi-browser"; +import PropTypes from "prop-types"; +import React from "react"; +import Form from "./ui/form/Form"; +import { + CONFIRM_PASSWORD_LABEL, + NEW_PASSWORD_LABEL +} from "../../constants/form/labels/input"; +import { + CONFIRM_PASSWORD, + NEW_PASSWORD +} from "../../constants/form/names/input"; +import LoadingSpinner from "./ui/LoadingSpinner"; +import { CHANGE } from "../../constants/form/labels/button"; + +export default class ModifyAccountForm extends Form { + static propTypes = { + /** + * Error to display if submit was a failure + */ + error: PropTypes.object, + + /** + * Is sign up info submitting + */ + isSubmitting: PropTypes.bool.isRequired + }; + + state = { + submitPressed: false, + data: { + newPassword: "", + confirmPassword: "" + }, + errors: {} + }; + + schema = { + newPassword: Joi.string() + .required() + .label(NEW_PASSWORD_LABEL) + .min(8), + confirmPassword: Joi.valid(Joi.ref(NEW_PASSWORD)) + .options({ + language: { any: { allowOnly: "must match password" } } + }) + .label(CONFIRM_PASSWORD_LABEL) + }; + + onSubmit = e => { + this.handleSubmit(e, this.props.onSubmit); + }; + + render() { + const { error, isSubmitting, success } = this.props; + return ( +
+
+
+
+

Change Password

+ {this.renderInput( + NEW_PASSWORD, + NEW_PASSWORD_LABEL, + NEW_PASSWORD_LABEL, + isSubmitting, + "password", + true + )} + {this.renderInput( + CONFIRM_PASSWORD, + CONFIRM_PASSWORD_LABEL, + CONFIRM_PASSWORD_LABEL, + isSubmitting, + "password", + true + )} + {isSubmitting && ( + + )} + {!isSubmitting && ( +
+
{error.message}
+
+ {success} +
+
+ {this.renderButton(CHANGE, "modify-account", "submit")} +
+
+ )} + +
+
+
+ ); + } +} diff --git a/frontend/src/components/common/Navbar.jsx b/frontend/src/components/common/Navbar.jsx index a6d43647..d2136d29 100644 --- a/frontend/src/components/common/Navbar.jsx +++ b/frontend/src/components/common/Navbar.jsx @@ -4,7 +4,12 @@ import { Nav, Navbar } from "react-bootstrap"; import { NavLink } from "react-router-dom"; import { ReactComponent as Logo } from "../../assets/images/logo.svg"; import { TOKEN_HEADER } from "../../constants/headers"; -import { FEEDBACKS_ROUTE_PAGE, FEEDBACK_ROUTE } from "../../constants/routes"; +import { + FEEDBACK_ROUTE, + SIGNUP_ROUTE, + FEEDBACKS_ROUTE, + ACCOUNT_ROUTE +} from "../../constants/routes"; import { signOut } from "../../services/http/authService"; import { newWindowLocation } from "../../utils/navigate"; @@ -46,12 +51,18 @@ export default class NavBar extends Component {