diff --git a/docker-compose.yml b/docker-compose.yml index 0a294fb..f7935aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,14 +31,6 @@ services: # - DEFAULT_ADMIN_NAME=Demo Demo # - DEFAULT_ADMIN_USERNAME=demo - # Email Notifications (https://nodemailer.com/smtp/) - # - SMTP_HOST= - # - SMTP_PORT=587 - # - SMTP_SECURE=true - # - SMTP_USER= - # - SMTP_PASSWORD= - # - SMTP_FROM="Demo Demo" - # - OIDC_ISSUER= # - OIDC_CLIENT_ID= # - OIDC_CLIENT_SECRET= @@ -52,8 +44,16 @@ services: # - OIDC_IGNORE_ROLES=true # - OIDC_ENFORCED=true - # - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx - # - SLACK_CHANNEL_ID=xxxxxxxxxx + # Email Notifications (https://nodemailer.com/smtp/) + # - SMTP_HOST= + # - SMTP_PORT=587 + # - SMTP_SECURE=true + # - SMTP_USER= + # - SMTP_PASSWORD= + # - SMTP_FROM="Demo Demo" + + # - SLACK_BOT_TOKEN= + # - SLACK_CHANNEL_ID= depends_on: postgres: condition: service_healthy diff --git a/server/.env.sample b/server/.env.sample index fb5a828..15d66ed 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -22,14 +22,6 @@ SECRET_KEY=notsecretkey # DEFAULT_ADMIN_NAME=Demo Demo # DEFAULT_ADMIN_USERNAME=demo -# Email Notifications (https://nodemailer.com/smtp/) -# SMTP_HOST= -# SMTP_PORT=587 -# SMTP_SECURE=true -# SMTP_USER= -# SMTP_PASSWORD= -# SMTP_FROM="Demo Demo" - # OIDC_ISSUER= # OIDC_CLIENT_ID= # OIDC_CLIENT_SECRET= @@ -43,6 +35,17 @@ SECRET_KEY=notsecretkey # OIDC_IGNORE_ROLES=true # OIDC_ENFORCED=true +# Email Notifications (https://nodemailer.com/smtp/) +# SMTP_HOST= +# SMTP_PORT=587 +# SMTP_SECURE=true +# SMTP_USER= +# SMTP_PASSWORD= +# SMTP_FROM="Demo Demo" + +# SLACK_BOT_TOKEN= +# SLACK_CHANNEL_ID= + ## Do not edit this TZ=UTC diff --git a/server/api/controllers/cards/delete.js b/server/api/controllers/cards/delete.js index e2b6d00..dad82de 100755 --- a/server/api/controllers/cards/delete.js +++ b/server/api/controllers/cards/delete.js @@ -47,6 +47,7 @@ module.exports = { card = await sails.helpers.cards.deleteOne.with({ record: card, + user: currentUser, request: this.req, }); diff --git a/server/api/helpers/actions/create-one.js b/server/api/helpers/actions/create-one.js index 773e0ce..b03adfd 100644 --- a/server/api/helpers/actions/create-one.js +++ b/server/api/helpers/actions/create-one.js @@ -1,5 +1,3 @@ -const services = require('../../services/slack'); - const valuesValidator = (value) => { if (!_.isPlainObject(value)) { return false; @@ -16,6 +14,30 @@ const valuesValidator = (value) => { return true; }; +const buildAndSendSlackMessage = async (user, card, action) => { + const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`; + + let markdown; + switch (action.type) { + case Action.Types.CREATE_CARD: + markdown = `${cardLink} was created by ${user.name} in *${action.data.list.name}*`; + + break; + case Action.Types.MOVE_CARD: + markdown = `${cardLink} was moved by ${user.name} to *${action.data.toList.name}*`; + + break; + case Action.Types.COMMENT_CARD: + markdown = `*${user.name}* commented on ${cardLink}:\n>${action.data.text}`; + + break; + default: + return; + } + + await sails.helpers.utils.sendSlackMessage(markdown); +}; + module.exports = { inputs: { values: { @@ -69,11 +91,9 @@ module.exports = { ), ); - const cardUrl = services.buildCardUrl(values.card); - const messageText = `*${inputs.values.user.name}* commented on ${cardUrl}:\n>${values.data.text}`; - services.sendSlackMessage(messageText).catch((error) => { - sails.log.error('Failed to send Slack message:', error.message); - }); + if (sails.config.custom.slackBotToken) { + buildAndSendSlackMessage(values.user, values.card, action); + } return action; }, diff --git a/server/api/helpers/cards/create-one.js b/server/api/helpers/cards/create-one.js index 0a04016..847a00c 100644 --- a/server/api/helpers/cards/create-one.js +++ b/server/api/helpers/cards/create-one.js @@ -1,5 +1,3 @@ -const services = require('../../services/slack'); - const valuesValidator = (value) => { if (!_.isPlainObject(value)) { return false; @@ -113,12 +111,6 @@ module.exports = { board: inputs.board, }); - const cardUrl = services.buildCardUrl(card); - const messageText = `${cardUrl} was created by ${values.creatorUser.name} in *${values.list.name}*`; - services.sendSlackMessage(messageText).catch((error) => { - sails.log.error('Failed to send Slack message:', error.message); - }); - return card; }, }; diff --git a/server/api/helpers/cards/delete-one.js b/server/api/helpers/cards/delete-one.js index 111247e..a947f73 100644 --- a/server/api/helpers/cards/delete-one.js +++ b/server/api/helpers/cards/delete-one.js @@ -1,4 +1,6 @@ -const services = require('../../services/slack'); +const buildAndSendSlackMessage = async (user, card) => { + await sails.helpers.utils.sendSlackMessage(`*${card.name}* was deleted by ${user.name}`); +}; module.exports = { inputs: { @@ -6,6 +8,10 @@ module.exports = { type: 'ref', required: true, }, + user: { + type: 'ref', + required: true, + }, request: { type: 'ref', }, @@ -24,10 +30,9 @@ module.exports = { inputs.request, ); - const messageText = `*${card.name}* was deleted by ${inputs.request.name}`; - services.sendSlackMessage(messageText).catch((error) => { - sails.log.error('Failed to send Slack message:', error.message); - }); + if (sails.config.custom.slackBotToken) { + buildAndSendSlackMessage(inputs.user, card); + } } return card; diff --git a/server/api/helpers/cards/update-one.js b/server/api/helpers/cards/update-one.js index a496a1e..4363f9b 100644 --- a/server/api/helpers/cards/update-one.js +++ b/server/api/helpers/cards/update-one.js @@ -1,5 +1,3 @@ -const services = require('../../services/slack'); - const valuesValidator = (value) => { if (!_.isPlainObject(value)) { return false; @@ -271,16 +269,6 @@ module.exports = { } } - const cardMoved = inputs.values.list !== undefined; - - if (cardMoved) { - const cardUrl = services.buildCardUrl(card); - const messageText = `${cardUrl} was moved by ${inputs.user.name} to *${inputs.list.name}*`; - services.sendSlackMessage(messageText).catch((error) => { - sails.log.error('Failed to send Slack message:', error.message); - }); - } - return card; }, }; diff --git a/server/api/helpers/notifications/create-one.js b/server/api/helpers/notifications/create-one.js index 271a917..f24e4bf 100644 --- a/server/api/helpers/notifications/create-one.js +++ b/server/api/helpers/notifications/create-one.js @@ -27,6 +27,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => { `from ${action.data.fromList.name} to ${action.data.toList.name} ` + `on ${board.name}

`, }; + break; case Action.Types.COMMENT_CARD: emailData = { @@ -37,6 +38,7 @@ const buildAndSendEmail = async (user, board, card, action, notifiableUser) => { `on ${board.name}

` + `

${action.data.text}

`, }; + break; default: return; diff --git a/server/api/helpers/utils/send-email.js b/server/api/helpers/utils/send-email.js index 5d9be72..7b8d08b 100644 --- a/server/api/helpers/utils/send-email.js +++ b/server/api/helpers/utils/send-email.js @@ -25,7 +25,7 @@ module.exports = { sails.log.info('Email sent: %s', info.messageId); } catch (error) { - sails.log.error(error); + sails.log.error(error); // TODO: provide description text? } }, }; diff --git a/server/api/helpers/utils/send-slack-message.js b/server/api/helpers/utils/send-slack-message.js new file mode 100644 index 0000000..573fcf8 --- /dev/null +++ b/server/api/helpers/utils/send-slack-message.js @@ -0,0 +1,53 @@ +const POST_MESSAGE_API_URL = 'https://slack.com/api/chat.postMessage'; + +module.exports = { + inputs: { + markdown: { + type: 'string', + required: true, + }, + }, + + async fn(inputs) { + const headers = { + Authorization: `Bearer ${sails.config.custom.slackBotToken}`, + 'Content-Type': 'application/json; charset=utf-8', + }; + + const body = { + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: inputs.markdown, + }, + }, + ], + channel: sails.config.custom.slackChannelId, + }; + + let response; + try { + response = await fetch(POST_MESSAGE_API_URL, { + headers, + method: 'POST', + body: JSON.stringify(body), + }); + } catch (error) { + sails.log.error(error); // TODO: provide description text? + return; + } + + if (!response.ok) { + sails.log.error('Error sending to Slack: %s', response.error); + return; + } + + const responseJson = await response.json(); + + if (!responseJson.ok) { + sails.log.error('Error sending to Slack: %s', responseJson.error); + } + }, +}; diff --git a/server/api/services/slack.js b/server/api/services/slack.js deleted file mode 100644 index 2aa5f54..0000000 --- a/server/api/services/slack.js +++ /dev/null @@ -1,61 +0,0 @@ -const slackPostUrl = 'https://slack.com/api/chat.postMessage'; -const channelId = process.env.SLACK_CHANNEL_ID; -const slackAPIToken = process.env.SLACK_BOT_TOKEN; -const plankaProdUrl = process.env.BASE_URL; - -async function sendSlackMessage(messageText) { - if (!slackAPIToken) { - throw new Error('No Slack BOT token found'); - } - - const postData = { - blocks: [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: messageText, - }, - }, - ], - channel: channelId, - }; - - const config = { - headers: { - 'Content-Type': 'application/json; charset=utf-8', - Authorization: `Bearer ${slackAPIToken}`, - }, - }; - - const response = await fetch(slackPostUrl, { - method: 'POST', - headers: config.headers, - body: JSON.stringify(postData), - }); - - if (!response.ok) { - sails.log.Error('Error sending to Slack :', response.error); - return Promise.reject(response); - } - - const responseText = await new Response(response.body).text(); - const jsonBody = JSON.parse(responseText); - if (!jsonBody.ok) { - sails.log.Error('Error sending to Slack :', jsonBody.error); - return Promise.reject(response); - } - - return response; -} - -function buildCardUrl(card) { - const url = `${plankaProdUrl}/cards/${card.id}`; - const cardUrl = `<${url}|${card.name}>`; - return cardUrl; -} - -module.exports = { - sendSlackMessage, - buildCardUrl, -}; diff --git a/server/config/custom.js b/server/config/custom.js index 7647324..ac344d0 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -34,13 +34,6 @@ module.exports.custom = { defaultAdminEmail: process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(), - smtpHost: process.env.SMTP_HOST, - smtpPort: process.env.SMTP_PORT || 587, - smtpSecure: process.env.SMTP_SECURE === 'true', - smtpUser: process.env.SMTP_USER, - smtpPassword: process.env.SMTP_PASSWORD, - smtpFrom: process.env.SMTP_FROM, - oidcIssuer: process.env.OIDC_ISSUER, oidcClientId: process.env.OIDC_CLIENT_ID, oidcClientSecret: process.env.OIDC_CLIENT_SECRET, @@ -58,4 +51,14 @@ module.exports.custom = { oidcRedirectUri: `${ sails.config.environment === 'production' ? process.env.BASE_URL : 'http://localhost:3000' }/oidc-callback`, + + smtpHost: process.env.SMTP_HOST, + smtpPort: process.env.SMTP_PORT || 587, + smtpSecure: process.env.SMTP_SECURE === 'true', + smtpUser: process.env.SMTP_USER, + smtpPassword: process.env.SMTP_PASSWORD, + smtpFrom: process.env.SMTP_FROM, + + slackBotToken: process.env.SLACK_BOT_TOKEN, + slackChannelId: process.env.SLACK_CHANNEL_ID, };